DotNetDupe 4.0.6
C++17/20 Implementation of the .NET Base Class Library (BCL)
Loading...
Searching...
No Matches
Path.cpp
Go to the documentation of this file.
1#include "pch.h"
2#include "System/IO/Path.h"
4#include <filesystem>
5#include <random>
6#include <string>
7#include <fstream>
8#include <algorithm>
9#include <initializer_list>
10#include <vector>
11
12#if defined(_WIN32)
13#include <windows.h>
14#include "Win32Internal.h"
15#undef GetTempFileName
16#undef GetTempPath
17using namespace DotNetDupe::System::Internal;
18#endif
19
20namespace fs = std::filesystem;
21
22namespace {
23 fs::path ToFsPath(const DotNetDupe::System::String& sPath) {
24#if defined(_WIN32)
26#else
27 return fs::path(sPath.GetRawString());
28#endif
29 }
30
31 DotNetDupe::System::String FromFsPath(const fs::path& path) {
32#if defined(_WIN32)
33 return DotNetDupe::System::String(DotNetDupe::System::Internal::StringConvertInternal::WCharToUtf8(path.wstring().c_str()).c_str());
34#else
35 return DotNetDupe::System::String(path.string().c_str());
36#endif
37 }
38}
39
40namespace DotNetDupe {
41 namespace System {
42 namespace IO {
43 String Path::ChangeExtension(const String& sFilePath, const String& sExtension) {
45 if (sFilePath.GetLength() <= 0 || sExtension.GetLength() <= 0)
46 throw ArgumentException("Invalid argument");
47 if (sFilePath.GetLength() <= sExtension.GetLength())
48 throw ArgumentException("Invalid argument");
49 auto sTempFilePath = sFilePath;
50 if (sFilePath [0] == '.')
51 sTempFilePath = sFilePath.Remove(0);
52
54 fs::path path = ToFsPath(sTempFilePath);
55#if defined(_WIN32)
57#else
58 path.replace_extension(sExtension.GetRawString());
59#endif
60 return FromFsPath(path);
61 }
62
63 String Path::Combine(const std::initializer_list<String> sPaths) {
65 auto indexedList = _init_list_with_indexer(sPaths);
66 fs::path rootPath = ToFsPath(indexedList [0]);
67 if (!rootPath.has_root_directory())
68 return indexedList [0];
69
71 String sCombinedPath("");
72 for (auto sPath : sPaths) {
73 sCombinedPath.Append(sPath);
74 sCombinedPath.Append(static_cast<char>(fs::path::preferred_separator));
75 }
76 return sCombinedPath.Remove(sCombinedPath.GetLength() - 1);
77 }
78
79 bool Path::EndsInDirectorySeparator(const String& sFilePath) {
81 if (sFilePath.IsEmpty()) return false;
82 char chLastChar = sFilePath[sFilePath.GetLength() - 1];
84 return chLastChar == '\\' || chLastChar == '/';
85 }
86
87 bool Path::Exists(const String& sFilePath) {
89 return fs::exists(ToFsPath(sFilePath));
90 }
91
94 if (sFilePath.IsEmpty())
95 return "";
96
98 fs::path path = ToFsPath(sFilePath);
99 return FromFsPath(path.parent_path());
100 }
101
102 String Path::GetFileName(const String& sFilePath) {
104 if (sFilePath.IsEmpty())
105 return "";
107 fs::path path = ToFsPath(sFilePath);
108 return FromFsPath(path.filename());
109 }
110
111 String Path::GetExtension(const String& sFilePath) {
113 if (sFilePath.IsEmpty())
114 return "";
115
117 fs::path path = ToFsPath(sFilePath);
118 return FromFsPath(path.extension());
119 }
120
123 if (sFilePath.IsEmpty())
124 return "";
125
127 fs::path path = ToFsPath(sFilePath);
128 return FromFsPath(path.stem());
129 }
130
133 if (sPath.IsEmpty())
134 return "";
135
137 return FromFsPath(fs::absolute(ToFsPath(sPath)));
138 }
139
142 static const std::vector<char> tempInvalidChars = {
143 '"', '<', '>', '|', '\0',
144 '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F',
145 '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', '\x1E', '\x1F',
146 ':', '*', '?', '\\', '/' };
147 Array<char> invalidChars((int)tempInvalidChars.size());
148 for (int iIndex = 0; iIndex < (int)tempInvalidChars.size(); iIndex++) invalidChars[iIndex] = tempInvalidChars[iIndex];
149 return invalidChars;
150 }
151
154 static const std::vector<char> tempInvalidChars = {
155 '|', '\0',
156 '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F',
157 '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', '\x1E', '\x1F' };
158 Array<char> invalidChars((int)tempInvalidChars.size());
159 for (int iIndex = 0; iIndex < (int)tempInvalidChars.size(); iIndex++) invalidChars[iIndex] = tempInvalidChars[iIndex];
160 return invalidChars;
161 }
162
164 {
166 const char chChars[] = "abcdefghijklmnopqrstuvwxyz0123456789";
167 const int nCharsLen = sizeof(chChars) / sizeof(char) - 1;
168
169 std::random_device rd;
170 std::mt19937 generator(rd());
171 std::uniform_int_distribution<> distribution(0, nCharsLen - 1);
172
173 char chRandomString[13];
174
175 for (int iIndex = 0; iIndex < 8; ++iIndex) {
176 chRandomString[iIndex] = chChars[distribution(generator)];
177 }
178 chRandomString[8] = '.';
179 for (int iIndex = 0; iIndex < 3; ++iIndex) {
180 chRandomString[9 + iIndex] = chChars[distribution(generator)];
181 }
182 chRandomString[12] = '\0';
183
184 return String(chRandomString);
185 }
186
188 {
190 if (sPath.IsEmpty())
191 return "";
192
194 if (sPath.StartsWith("\\", false))
195 {
196 int nLen = sPath.GetLength();
197 if (nLen > 2)
198 {
199 int iIdx = sPath.IndexOf("\\", 2, false);
200 if (iIdx != -1)
201 {
202 iIdx = sPath.IndexOf("\\", iIdx + 1, false);
203 if (iIdx != -1)
204 return sPath.Substring(0, iIdx);
205 }
206 }
207 return sPath;
208 }
209
210 fs::path p = ToFsPath(sPath);
211 return FromFsPath(p.root_path());
212 }
213
214 String Path::GetRelativePath(const String& sRelativeTo, const String& sPath) {
216 const fs::path fsRelativeTo = ToFsPath(GetFullPath(sRelativeTo));
217 const fs::path fsPath = ToFsPath(GetFullPath(sPath));
218
219#if defined(_WIN32)
220 if (fsRelativeTo.root_name() != fsPath.root_name()) {
221 return sPath;
222 }
223#endif
224
225 return FromFsPath(fs::relative(fsPath, fsRelativeTo));
226 }
227
228 static String GenerateRandomTempFileName(const String& sTempDir) {
230 std::random_device rd;
231 std::mt19937 generator(rd());
232 std::uniform_int_distribution<int> dist(0, 35);
233 const char alphabet[] = "abcdefghijklmnopqrstuvwxyz0123456789";
234 for (int i = 0; i < 100; ++i) {
235 char name[9];
236 for (int j = 0; j < 8; ++j) name[j] = alphabet[dist(generator)];
237 name[8] = '\0';
238 fs::path fp = ToFsPath(sTempDir) / (String(name) + ".tmp").GetRawString();
239 if (!fs::exists(fp)) {
240 std::ofstream ofs(fp);
241 if (ofs) return FromFsPath(fp);
242 }
243 }
244 throw IOException("Could not create a unique temporary file.");
245 }
246
251
254#if defined(_WIN32)
255 wchar_t buffer [MAX_PATH];
256 ::GetTempPathW(MAX_PATH, buffer);
257 String sPath(WCharToUtf8(buffer).c_str());
258 if (!sPath.EndsWith('\\', false)) sPath.Append('\\');
259 return sPath;
260#else
261 const char* pTmpDir = getenv("TMPDIR");
262 String sPath = pTmpDir ? String(pTmpDir) : String("/tmp/");
263 if (!sPath.EndsWith('/', false)) sPath.Append('/');
264 return sPath;
265#endif
266 }
267
268 bool Path::HasExtension(const String& sPath) {
270 if (sPath.IsEmpty()) return false;
271 String sFilenameStr = GetFileName(sPath);
272 if (sFilenameStr.IsEmpty() || sFilenameStr == "." || sFilenameStr == "..") return false;
273 std::string sFilename((const char*)sFilenameStr);
274 auto nDotPos = sFilename.rfind('.');
275 return (nDotPos != std::string::npos && nDotPos != sFilename.length() - 1);
276 }
277
280 if (sPath.IsEmpty()) return false;
282 return ToFsPath(sPath).is_absolute();
283 }
284
285 bool Path::IsPathRooted(const String& sPath) {
287 if (sPath.IsEmpty()) return false;
288 int nLen = sPath.GetLength();
290 if (sPath[0] == '\\' || sPath[0] == '/') return true;
291 if (nLen >= 2 && sPath[1] == ':' && std::isalpha(static_cast<unsigned char>(sPath[0]))) {
292 return (nLen >= 3 && (sPath[2] == '\\' || sPath[2] == '/'));
293 }
294 return false;
295 }
296
297 String Path::Join(const std::initializer_list<String> sPaths) {
299 String sResult("");
300 if (TryJoin(sPaths, sResult)) return sResult;
301 throw ArgumentException("Invalid character in path.");
302 }
303
304 static bool ValidatePathChars(const std::initializer_list<String>& sPaths) {
306 auto invalidPathChars = Path::GetInvalidPathChars();
307 for (const auto& sPath : sPaths) {
308 for (auto ch : invalidPathChars) {
309 if (sPath.Contains(ch)) return false;
310 }
311 }
312 return true;
313 }
314
315 static void AppendPathSegment(String& sJoined, const String& sPath) {
317 if (sPath.IsEmpty()) return;
319 if (sJoined.IsEmpty()) {
320 sJoined = sPath;
321 } else {
322 char chLast = sJoined[sJoined.GetLength() - 1];
323 if (chLast != '\\' && chLast != '/') sJoined.Append(static_cast<char>(fs::path::preferred_separator));
324 if (sPath[0] == '\\' || sPath[0] == '/') {
325 sJoined.Append(sPath.Substring(1, sPath.GetLength() - 1));
326 } else {
327 sJoined.Append(sPath);
328 }
329 }
330 }
331
332 bool Path::TryJoin(const std::initializer_list<String> sPaths, String& sResult) {
334 if (!ValidatePathChars(sPaths)) {
335 sResult = String("");
336 return false;
337 }
338 String sJoinedPath("");
339 for (const auto& sPath : sPaths) AppendPathSegment(sJoinedPath, sPath);
340 sResult = sJoinedPath;
341 return true;
342 }
343
346 if (sPath.IsEmpty()) return sPath;
347 if (Path::IsPathRooted(sPath) && sPath.GetLength() <= 3) return sPath;
348
350 char chLastChar = sPath[sPath.GetLength() - 1];
351 if (chLastChar == '\\' || chLastChar == '/') {
352 return sPath.Substring(0, sPath.GetLength() - 1);
353 }
354 return sPath;
355 }
356
359 return static_cast<char>(fs::path::preferred_separator);
360 }
361
364 return (fs::path::preferred_separator == '/') ? '\\' : '/';
365 }
366
369 return ':';
370 }
371
374#if defined(_WIN32)
375 return ';';
376#else
377 return ':';
378#endif
379 }
380 }
381 }
382}
The exception that is thrown when an I/O error occurs.
ArgumentException(const String &sMessage)
Initializes a new instance of the ArgumentException class with a specified error message.
Provides methods for creating, manipulating, searching, and sorting arrays, thereby serving as the ba...
Definition Array.h:29
IOException()
Initializes a new instance of the IOException class with a default message.
Definition Exception.cpp:56
static bool Exists(const String &sFilePath)
Determines whether the given path refers to an existing file or directory on disk.
Definition Path.cpp:87
static String ChangeExtension(const String &sFilePath, const String &sExtension)
Changes the extension of a path string.
Definition Path.cpp:43
static String GetRelativePath(const String &sRelativeTo, const String &sPath)
Returns a relative path from one path to another.
Definition Path.cpp:214
static bool EndsInDirectorySeparator(const String &sFilePath)
Returns a value that indicates whether the specified path ends in a directory separator.
Definition Path.cpp:79
static char GetDirectorySeparatorChar()
Provides a platform-specific character used to separate directory levels in a path.
Definition Path.cpp:357
static Array< char > GetInvalidPathChars()
Gets an array of characters that cannot be present in path names.
Definition Path.cpp:152
static bool TryJoin(const std::initializer_list< String > sPaths, String &sResult)
Attempts to concatenate an array of paths into a single path.
Definition Path.cpp:332
static String GetExtension(const String &sFilePath)
Returns the extension of the specified path string.
Definition Path.cpp:111
static String GetRandomFileName()
Returns a random folder name or file name.
Definition Path.cpp:163
static String TrimEndingDirectorySeparator(const String &sPath)
Trims one trailing directory separator beyond the root of the specified path.
Definition Path.cpp:344
static String GetTempPath()
Returns the path of the current user's temporary folder.
Definition Path.cpp:252
static String GetFullPath(const String &sPath)
Returns the absolute path for the specified path string.
Definition Path.cpp:131
static String Combine(const std::initializer_list< String > sPaths)
Combines an array of strings into a path.
Definition Path.cpp:63
static String GetDirectoryName(const String &sFilePath)
Returns the directory information for the specified path string.
Definition Path.cpp:92
static char GetPathSeparator()
A platform-specific separator character used to separate path strings in environment variables.
Definition Path.cpp:372
static String GetPathRoot(const String &sPath)
Gets the root directory information from the path contained in the specified string.
Definition Path.cpp:187
static char GetAltDirectorySeparatorChar()
Provides a platform-specific alternate character used to separate directory levels.
Definition Path.cpp:362
static Array< char > GetInvalidFileNameChars()
Gets an array of characters that cannot be present in file names.
Definition Path.cpp:140
static String Join(const std::initializer_list< String > sPaths)
Concatenates an array of paths into a single path.
Definition Path.cpp:297
static char GetVolumeSeparatorChar()
Provides a platform-specific volume separator character.
Definition Path.cpp:367
static String GetTempFileName()
Creates a uniquely named, zero-byte temporary file on disk and returns the full path of that file.
Definition Path.cpp:247
static bool IsPathFullyQualified(const String &sPath)
Returns a value that indicates whether the specified path is fully qualified.
Definition Path.cpp:278
static String GetFileName(const String &sFilePath)
Returns the file name and extension of the specified path string.
Definition Path.cpp:102
static bool IsPathRooted(const String &sPath)
Returns a value that indicates whether the specified path string contains a root.
Definition Path.cpp:285
static bool HasExtension(const String &sPath)
Determines whether a path includes a file name extension.
Definition Path.cpp:268
static String GetFileNameWithoutExtension(const String &sFilePath)
Returns the file name of the specified path string without the extension.
Definition Path.cpp:121
static std::wstring Utf8ToWChar(const char *pUtf8Str)
static std::string WCharToUtf8(const wchar_t *pWStr)
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
Definition String.h:74
String Substring(int iStartIndex) const
Definition String.cpp:659
bool StartsWith(const String &sPrefix) const
Definition String.cpp:610
String Remove(int iStartIndex) const
Definition String.cpp:519
bool EndsWith(char ch, bool bIgnoreCase) const
Definition String.cpp:315
String()
Initializes a new instance of the String class to an empty string.
Definition String.cpp:64
String & Append(const char ch)
Definition String.cpp:380
int IndexOf(const String &sSubstring) const
Definition String.cpp:341
const char * GetRawString() const
Definition String.cpp:230
bool Contains(char ch) const
Definition String.cpp:292
static bool ValidatePathChars(const std::initializer_list< String > &sPaths)
Definition Path.cpp:304
static String GenerateRandomTempFileName(const String &sTempDir)
Definition Path.cpp:228
static void AppendPathSegment(String &sJoined, const String &sPath)
Definition Path.cpp:315
std::string WCharToUtf8(const wchar_t *pWStr)
_init_list_with_indexer(const std::initializer_list< T > &_list)
Constructs a wrapper over the provided initializer list.
Definition Utils.h:13