carb/filesystem/FindFiles.h
File members: carb/filesystem/FindFiles.h
// Copyright (c) 2020-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "../extras/Path.h"
#include "../extras/StringProcessor.h"
#include "../extras/StringUtils.h"
#include "IFileSystem.h"
#include "../Format.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);
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);
struct FindFilesArgs
{
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;
IFileSystem* fs;
FindFilesOnFilterNonCanonicalFn* onFilterNonCanonical;
void* onFilterNonCanonicalContext;
FindFilesOnMatchedFn* onMatched;
void* onMatchedContext;
FindFilesOnExcludedFn* onExcluded;
void* onExcludedContext;
FindFilesOnSkippedFn* onSkipped;
void* onSkippedContext;
FindFilesOnSearchPathFn* onSearchPath;
void* onSearchPathContext;
FindFilesFlag flags;
};
inline bool findFiles(const FindFilesArgs& args);
#ifndef DOXYGEN_BUILD
namespace detail
{
struct FindFilesContext
{
const FindFilesArgs* args;
IFileSystem* fs;
};
inline WalkAction onFile(const DirectoryItemInfo* const info, void* userData)
{
if (info->type == DirectoryItemType::eFile)
{
auto context = reinterpret_cast<const FindFilesContext*>(userData);
// 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(info->path, context->args->onFilterNonCanonicalContext);
switch (action)
{
case WalkAction::eStop:
return WalkAction::eStop;
case WalkAction::eSkip:
return WalkAction::eContinue;
default: // eContinue
break; // fall-through
}
}
std::string canonical = context->fs->makeCanonicalPath(info->path);
extras::Path path(canonical);
std::string target;
if (context->args->flags & kFindFilesFlagMatchStem)
{
target = path.getStem();
}
else
{
target = path.getFilename();
}
const char* toMatch = target.c_str();
// note, even if a pattern matches, we still have to loop through all of "ignorePrefixesCount" looking for an
// exclusion since exclusions take precedent.
bool matched = false;
for (int32_t i = -1; i < int32_t(context->args->ignorePrefixesCount); ++i)
{
const char* prefix = "";
if (-1 != i)
{
prefix = context->args->ignorePrefixes[i];
}
if (extras::startsWith(toMatch, prefix))
{
const char* strippedMatch = toMatch + std::strlen(prefix);
if (omni::str::matchWildcards(
strippedMatch, context->args->matchWildcards, context->args->matchWildcardsCount))
{
if (omni::str::matchWildcards(
strippedMatch, context->args->excludeWildcards, context->args->excludeWildcardsCount))
{
if (context->args->onExcluded)
{
context->args->onExcluded(canonical.c_str(), context->args->onExcludedContext);
}
return WalkAction::eContinue; // exclusion takes precedent. ignore the file and keep searching
}
else
{
matched = true;
}
}
}
}
if (matched)
{
if (context->args->onMatched)
{
context->args->onMatched(canonical.c_str(), context->args->onMatchedContext);
}
}
else
{
if (context->args->onSkipped)
{
context->args->onSkipped(canonical.c_str(), context->args->onSkippedContext);
}
}
}
return WalkAction::eContinue;
}
} // namespace detail
#endif
inline bool findFiles(const FindFilesArgs& args)
{
if (!args.searchPaths || !args.searchPathsCount)
{
CARB_LOG_ERROR("searchPath must be specified");
return false;
}
if (!args.matchWildcards || !args.matchWildcardsCount)
{
CARB_LOG_ERROR("match wildcard must be specified");
return false;
}
auto framework = getFramework();
if (!framework)
{
CARB_LOG_ERROR("carb::Framework not active");
return false;
}
IFileSystem* fs;
if (args.fs)
{
fs = framework->verifyInterface<IFileSystem>(args.fs);
if (!fs)
{
CARB_LOG_ERROR("incompatible carb::filesystem::IFileSystem");
return false;
}
}
else
{
fs = framework->tryAcquireInterface<IFileSystem>();
if (!fs)
{
CARB_LOG_ERROR("unable to acquire carb::filesystem::IFileSystem");
return false;
}
}
detail::FindFilesContext userData = { &args, fs };
for (uint32_t i = 0; i < args.searchPathsCount; ++i)
{
const char* dir = args.searchPaths[i];
std::string dirWithEnv;
if (args.flags & kFindFilesFlagReplaceEnvironmentVariables)
{
dirWithEnv = extras::replaceEnvironmentVariables(dir);
dir = dirWithEnv.c_str();
}
extras::Path path{ dir };
const char* fullPath = path.getStringBuffer();
std::string tmp;
if (!path.isAbsolute())
{
tmp = carb::fmt::format("{}/{}", fs->getAppDirectoryPath(), dir);
fullPath = tmp.c_str();
}
if (args.onSearchPath)
{
args.onSearchPath(fullPath, args.onSearchPathContext);
}
if (args.flags & kFindFilesFlagRecursive)
{
fs->forEachDirectoryItemRecursive(fullPath, detail::onFile, const_cast<detail::FindFilesContext*>(&userData));
}
else
{
fs->forEachDirectoryItem(fullPath, detail::onFile, &userData);
}
}
return true;
}
} // namespace filesystem
} // namespace carb