FindFiles.h#

Fully qualified name: carb/filesystem/FindFiles.h

File members: carb/filesystem/FindFiles.h

// SPDX-FileCopyrightText: Copyright (c) 2020-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.

#pragma once

#include "../extras/Path.h"
#include "../extras/StringProcessor.h"
#include "../extras/StringUtils.h"
#include "FileSystemUtils.h"
#include "../Format.h"
#include "../cpp/Span.h"

#include "../../omni/str/Wildcard.h"

#include <cstdint>
#include <cstring>

namespace carb
{
namespace filesystem
{

using FindFilesFlag = uint32_t;

constexpr FindFilesFlag kFindFilesFlagNone = 0x0;

constexpr FindFilesFlag kFindFilesFlagRecursive = (1 << 0);

constexpr FindFilesFlag kFindFilesFlagMatchStem = (1 << 1);

constexpr FindFilesFlag kFindFilesFlagReplaceEnvironmentVariables = (1 << 2);

#if CARB_VERSION_ATLEAST(carb_filesystem_IFileSystem, 2, 0)
using FindFilesOnFilterNonCanonicalFn = WalkAction(cpp::zstring_view path, void* userData);

using FindFilesOnMatchedFn = void(cpp::zstring_view canonical, void* userData);

using FindFilesOnExcludedFn = void(cpp::zstring_view canonical, void* userData);

using FindFilesOnSkippedFn = void(cpp::zstring_view canonical, void* userData);

using FindFilesOnSearchPathFn = void(cpp::zstring_view path, void* userData);

namespace detail
{
using StrType = cpp::zstring_view;
inline cpp::zstring_view toCbType(cpp::zstring_view s)
{
    return s;
}
} // namespace detail
#else
using FindFilesOnFilterNonCanonicalFn = WalkAction(const char* path, void* userData);
using FindFilesOnMatchedFn = void(const char* canonical, void* userData);
using FindFilesOnExcludedFn = void(const char* canonical, void* userData);
using FindFilesOnSkippedFn = void(const char* canonical, void* userData);
using FindFilesOnSearchPathFn = void(const char* path, void* userData);

namespace detail
{
using StrType = const char*;
inline const char* toCbType(cpp::zstring_view s)
{
    return s.c_str();
}
} // namespace detail
#endif

struct FindFilesArgs
{
#if CARB_VERSION_ATLEAST(carb_filesystem_IFileSystem, 2, 0)
#    define CARBLOCAL_SPAN_SIZE(first, second) uint32_t((first).size())

    cpp::span<const cpp::string_view> searchPaths;

    cpp::span<const cpp::string_view> matchWildcards;

    cpp::span<const cpp::string_view> excludeWildcards;

    cpp::span<const cpp::string_view> ignorePrefixes;
#else
#    define CARBLOCAL_SPAN_SIZE(first, second) (second)

    const char* const* searchPaths;
    uint32_t searchPathsCount;

    const char* const* matchWildcards;
    uint32_t matchWildcardsCount;

    const char* const* excludeWildcards;
    uint32_t excludeWildcardsCount;

    const char* const* ignorePrefixes;
    uint32_t ignorePrefixesCount;
#endif

    IFileSystem* fs;

    FindFilesOnFilterNonCanonicalFn* onFilterNonCanonical;
    void* onFilterNonCanonicalContext;

    FindFilesOnMatchedFn* onMatched;
    void* onMatchedContext;

    FindFilesOnExcludedFn* onExcluded;
    void* onExcludedContext;

    FindFilesOnSkippedFn* onSkipped;
    void* onSkippedContext;

    FindFilesOnSearchPathFn* onSearchPath;
    void* onSearchPathContext;

    FindFilesFlag flags;
};

namespace detail
{
enum Result
{
    eExcluded = -1,
    eNotMatched,
    eMatched
};
#if CARB_VERSION_ATLEAST(carb_filesystem_IFileSystem, 2, 0)
inline Result matched(const FindFilesArgs& args, cpp::zstring_view canonical, cpp::zstring_view target)
{
    bool matched = false;
    for (size_t s = 0; s <= args.ignorePrefixes.size(); ++s)
    {
        cpp::string_view prefix = s > 0 ? args.ignorePrefixes[s - 1] : cpp::string_view{};
        if (target.starts_with(prefix))
        {
            target.remove_prefix(prefix.size());
            if (omni::str::matchWildcards(target, args.matchWildcards))
            {
                if (omni::str::matchWildcards(target, args.excludeWildcards))
                {
                    if (args.onExcluded)
                        args.onExcluded(canonical, args.onExcludedContext);
                    return eExcluded;
                }
                matched = true; // keep checking other exclusions
            }
        }
    }
    return matched ? eMatched : eNotMatched;
}
#else
inline Result matched(const FindFilesArgs& args, cpp::zstring_view canonical, cpp::zstring_view target)
{
    bool matched = false;
    for (size_t s = 0; s <= args.ignorePrefixesCount; ++s)
    {
        cpp::string_view prefix =
            s > 0 ? cpp::string_view(cpp::unsafe_length, args.ignorePrefixes[s - 1]) : cpp::string_view{};
        if (target.starts_with(prefix))
        {
            target.remove_prefix(prefix.size());
            if (omni::str::matchWildcards(target.c_str(), args.matchWildcards, args.matchWildcardsCount))
            {
                if (omni::str::matchWildcards(target.c_str(), args.excludeWildcards, args.excludeWildcardsCount))
                {
                    if (args.onExcluded)
                        args.onExcluded(canonical.c_str(), args.onExcludedContext);
                    return eExcluded;
                }
                matched = true; // keep checking other exclusions
            }
        }
    }
    return matched ? eMatched : eNotMatched;
}
#endif
} // namespace detail

inline bool findFiles(const FindFilesArgs& args);

#ifndef DOXYGEN_BUILD
namespace detail
{

struct FindFilesContext
{
    const FindFilesArgs* args;
    IFileSystem* fs;
};

inline WalkAction onFile(const FileInfo& info, cpp::zstring_view inPath, const FindFilesContext& context)
{
    if (info.type == DirectoryItemType::eFile)
    {
        // the canonicalization of a file is expensive.  here we give the user a chance to
        // tell us if we should canonicalize the file
        if (context.args->onFilterNonCanonical)
        {
            WalkAction action =
                context.args->onFilterNonCanonical(toCbType(inPath), context.args->onFilterNonCanonicalContext);
            switch (action)
            {
                case WalkAction::eStop:
                    return WalkAction::eStop;

                case WalkAction::eSkip:
                    return WalkAction::eContinue;

                default: // eContinue
                    break; // fall-through
            }
        }

        auto canonical = makeCanonicalPath(context.fs, inPath).value_or("");

        extras::Path path(canonical);
        const std::string target =
            ((context.args->flags & kFindFilesFlagMatchStem) ? path.getStem() : path.getFilename()).getString();

        switch (matched(*context.args, canonical, target))
        {
            case eExcluded:
                return WalkAction::eContinue;
            case eMatched:
                if (context.args->onMatched)
                    context.args->onMatched(toCbType(canonical), context.args->onMatchedContext);
                break;
            case eNotMatched:
                if (context.args->onSkipped)
                    context.args->onSkipped(toCbType(canonical), context.args->onSkippedContext);
                break;
        }
    }

    return WalkAction::eContinue;
}

} // namespace detail
#endif

inline bool findFiles(const FindFilesArgs& args)
{
    if (!CARBLOCAL_SPAN_SIZE(args.searchPaths, args.searchPathsCount))
    {
        CARB_LOG_ERROR(CARB_LOG_DEFAULT_CHANNEL, "searchPath must be specified");
        return false;
    }

    if (!CARBLOCAL_SPAN_SIZE(args.matchWildcards, args.matchWildcardsCount))
    {
        CARB_LOG_ERROR(CARB_LOG_DEFAULT_CHANNEL, "match wildcard must be specified");
        return false;
    }

    auto framework = getFramework();
    if (!framework)
    {
        CARB_LOG_ERROR(CARB_LOG_DEFAULT_CHANNEL, "carb::Framework not active");
        return false;
    }

    IFileSystem* fs;
    if (args.fs)
    {
        fs = framework->verifyInterface<IFileSystem>(args.fs);
        if (!fs)
        {
            CARB_LOG_ERROR(CARB_LOG_DEFAULT_CHANNEL, "incompatible carb::filesystem::IFileSystem");
            return false;
        }
    }
    else
    {
        fs = framework->tryAcquireInterface<IFileSystem>();
        if (!fs)
        {
            CARB_LOG_ERROR(CARB_LOG_DEFAULT_CHANNEL, "unable to acquire carb::filesystem::IFileSystem");
            return false;
        }
    }

    detail::FindFilesContext userData = { &args, fs };
    for (uint32_t i = 0; i < CARBLOCAL_SPAN_SIZE(args.searchPaths, args.searchPathsCount); ++i)
    {
        auto dir = args.searchPaths[i];
        std::string dirWithEnv;
        if (args.flags & kFindFilesFlagReplaceEnvironmentVariables)
        {
            dirWithEnv = extras::replaceEnvironmentVariables(std::string(dir));
            dir = detail::toCbType(dirWithEnv);
        }

        extras::Path path{ dir };
        std::string fullPath =
            path.isAbsolute() ? path.getString() : carb::fmt::format("{}/{}", fs->getAppDirectoryPath(), path.c_str());

        if (args.onSearchPath)
        {
            args.onSearchPath(detail::toCbType(fullPath), args.onSearchPathContext);
        }

        using namespace std::placeholders;
        (void)detail::forEachDirectoryItem(fs, fullPath, !!(args.flags & kFindFilesFlagRecursive),
                                           std::bind(detail::onFile, _1, _2, std::ref(userData)));
    }

    return true;
}

} // namespace filesystem
} // namespace carb

#undef CARBLOCAL_SPAN_SIZE