Files
2025-12-10 14:38:26 -08:00

201 lines
7.4 KiB
C++

// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
#include "file_path.h"
#include "zstring.h"
using namespace zen;
std::optional<PathComponents> zen::parsePathComponents(const Zstring& itemPath)
{
auto doParse = [&](int sepCountVolumeRoot, bool rootWithSep) -> std::optional<PathComponents>
{
assert(sepCountVolumeRoot > 0);
const Zstring itemPathPf = appendSeparator(itemPath); //simplify analysis of root without separator, e.g. \\server-name\share
for (auto it = itemPathPf.begin(); it != itemPathPf.end(); ++it)
if (*it == FILE_NAME_SEPARATOR)
if (--sepCountVolumeRoot == 0)
{
Zstring rootPath(itemPathPf.begin(), rootWithSep ? it + 1 : it);
Zstring relPath(it + 1, itemPathPf.end());
trim(relPath, TrimSide::both, [](Zchar c) { return c == FILE_NAME_SEPARATOR; });
return PathComponents{std::move(rootPath), std::move(relPath)};
}
return {};
};
std::optional<PathComponents> pc; //"/media/zenju/" and "/Volumes/" should not fail to parse
if (!pc && startsWith(itemPath, "/mnt/")) //e.g. /mnt/DEVICE_NAME
pc = doParse(3 /*sepCountVolumeRoot*/, false /*rootWithSep*/);
if (!pc && startsWith(itemPath, "/media/")) //Ubuntu: e.g. /media/zenju/DEVICE_NAME
if (const std::optional<Zstring> username = getEnvironmentVar("USER"))
if (startsWith(itemPath, std::string("/media/") + *username + "/"))
pc = doParse(4 /*sepCountVolumeRoot*/, false /*rootWithSep*/);
if (!pc && startsWith(itemPath, "/run/media/")) //CentOS, Suse: e.g. /run/media/zenju/DEVICE_NAME
if (const std::optional<Zstring> username = getEnvironmentVar("USER"))
if (startsWith(itemPath, std::string("/run/media/") + *username + "/"))
pc = doParse(5 /*sepCountVolumeRoot*/, false /*rootWithSep*/);
if (!pc && startsWith(itemPath, "/run/user/")) //Ubuntu, e.g.: /run/user/1000/gvfs/smb-share:server=192.168.62.145,share=folder
{
Zstring tmp(itemPath.begin() + strLength("/run/user/"), itemPath.end());
tmp = beforeFirst(tmp, "/gvfs/", IfNotFoundReturn::none);
if (!tmp.empty() && std::all_of(tmp.begin(), tmp.end(), [](const char c) { return isDigit(c); }))
/**/pc = doParse(6 /*sepCountVolumeRoot*/, false /*rootWithSep*/);
}
if (!pc && startsWith(itemPath, "/"))
pc = doParse(1 /*sepCountVolumeRoot*/, true /*rootWithSep*/);
return pc;
}
std::optional<Zstring> zen::getParentFolderPath(const Zstring& itemPath)
{
if (const std::optional<PathComponents> pc = parsePathComponents(itemPath))
{
if (pc->relPath.empty())
return std::nullopt;
return appendPath(pc->rootPath, beforeLast(pc->relPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none));
}
assert(itemPath.empty());
return std::nullopt;
}
Zstring zen::getFileExtension(const ZstringView filePath)
{
const ZstringView fileName = afterLast(filePath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all);
return Zstring(afterLast(fileName, Zstr('.'), IfNotFoundReturn::none));
}
Zstring zen::appendSeparator(Zstring path) //support rvalue references!
{
assert(!endsWith(path, FILE_NAME_SEPARATOR == Zstr('/') ? Zstr('\\' ) : Zstr('/' )));
if (!endsWith(path, FILE_NAME_SEPARATOR))
path += FILE_NAME_SEPARATOR;
return path; //returning a by-value parameter => RVO if possible, r-value otherwise!
}
bool zen::isValidRelPath(const Zstring& relPath)
{
//relPath is expected to use FILE_NAME_SEPARATOR!
if constexpr (FILE_NAME_SEPARATOR != Zstr('/' )) if (contains(relPath, Zstr('/' ))) return false;
if constexpr (FILE_NAME_SEPARATOR != Zstr('\\')) if (contains(relPath, Zstr('\\'))) return false;
const Zchar doubleSep[] = {FILE_NAME_SEPARATOR, FILE_NAME_SEPARATOR, 0};
return !startsWith(relPath, FILE_NAME_SEPARATOR) && !endsWith(relPath, FILE_NAME_SEPARATOR) &&
!contains(relPath, doubleSep);
}
Zstring zen::appendPath(const Zstring& basePath, const Zstring& relPath)
{
assert(isValidRelPath(relPath));
if (relPath.empty())
return basePath; //with or without path separator, e.g. C:\ or C:\folder
//assert(!basePath.empty());
if (basePath.empty()) //basePath might be a relative path, too!
return relPath;
if (endsWith(basePath, FILE_NAME_SEPARATOR))
return basePath + relPath;
Zstring output = basePath;
output.reserve(basePath.size() + 1 + relPath.size()); //append all three strings using a single memory allocation
return std::move(output) + FILE_NAME_SEPARATOR + relPath; //
}
/* https://docs.microsoft.com/de-de/windows/desktop/Intl/handling-sorting-in-your-applications
Perf test: compare strings 10 mio times; 64 bit build
-----------------------------------------------------
string a = "Fjk84$%kgfj$%T\\\\Gffg\\gsdgf\\fgsx----------d-"
string b = "fjK84$%kgfj$%T\\\\gfFg\\gsdgf\\fgSy----------dfdf"
Windows (UTF16 wchar_t)
4 ns | wcscmp
67 ns | CompareStringOrdinalFunc+ + bIgnoreCase
314 ns | LCMapString + wmemcmp
OS X (UTF8 char)
6 ns | strcmp
98 ns | strcasecmp
120 ns | strncasecmp + std::min(sizeLhs, sizeRhs);
856 ns | CFStringCreateWithCString + CFStringCompare(kCFCompareCaseInsensitive)
1110 ns | CFStringCreateWithCStringNoCopy + CFStringCompare(kCFCompareCaseInsensitive)
________________________
time per call | function */
std::weak_ordering zen::compareNativePath(const Zstring& lhs, const Zstring& rhs)
{
assert(!contains(lhs, Zchar('\0'))); //don't expect embedded nulls!
assert(!contains(rhs, Zchar('\0'))); //
return lhs <=> rhs;
}
namespace
{
constinit Global<std::unordered_map<Zstring, Zstring>> globalEnvVars;
}
std::optional<Zstring> zen::getEnvironmentVar(const ZstringView name)
{
/* const char* buffer = ::getenv(name); => NO! *not* thread-safe: returns pointer to internal memory!
might change after setenv(), allegedly possible even after another getenv()!
getenv_s() to the rescue!? not implemented on GCC, apparently *still* not threadsafe!!!
=> *eff* this: make a global copy during start up! */
globalEnvVars.setOnce([]
{
assert(runningOnMainThread());
auto envVars = std::make_unique<std::unordered_map<Zstring, Zstring>>();
if (char** line = environ)
for (; *line; ++line)
{
const std::string_view l(*line);
envVars->emplace(beforeFirst(l, '=', IfNotFoundReturn::all),
afterFirst(l, '=', IfNotFoundReturn::none));
}
return envVars;
});
if (std::shared_ptr<std::unordered_map<Zstring, Zstring>> envVars = globalEnvVars.get())
{
if (const auto it = envVars->find(name);
it != envVars->end())
return it->second;
}
else
assert(false); //access during global shutdown => SOL!
return {};
}