1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
|
// *****************************************************************************
// * 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(), [](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
{
std::unordered_map<Zstring, Zstring> getAllEnvVars()
{
assert(runningOnMainThread());
std::unordered_map<Zstring, Zstring> envVars;
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;
}
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([] { return std::make_unique<std::unordered_map<Zstring, Zstring>>(getAllEnvVars()); });
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 {};
}
|