Plasma Engine  2.0
Loading...
Searching...
No Matches
FileSystemMirror.h
1#pragma once
2
3#include <Foundation/Containers/DynamicArray.h>
4#include <Foundation/IO/OSFile.h>
5#include <Foundation/Types/UniquePtr.h>
6
7// A general problem when implementing a directory watcher is, that moving a folder out of the watched directory only communicates
8// Which folder was moved (but not to where, nor its contents). This means when a folder is moved out of view,
9// this needs to be treated as a delete. At the point of the move, it is no longer possible to query the contents of the folder.
10// So a in memory copy of the file system is required in order to correctly implement a directory watcher.
11template <typename T>
13{
14public:
15 enum class Type
16 {
17 File,
18 Directory
19 };
20
21 struct DirEntry
22 {
23 plMap<plString, DirEntry> m_subDirectories;
24 plMap<plString, T> m_files;
25 };
26
29
30 // \brief Adds the directory, and all files in it recursively.
31 plResult AddDirectory(plStringView sPath, bool* out_pDirectoryExistsAlready = nullptr);
32
33 // \brief Adds a file. Creates directories if they do not exist.
34 plResult AddFile(plStringView sPath, const T& value, bool* out_pFileExistsAlready, T* out_pOldValue);
35
36 // \brief Removes a file.
37 plResult RemoveFile(plStringView sPath);
38
39 // \brief Removes a directory. Deletes any files & directories inside.
40 plResult RemoveDirectory(plStringView sPath);
41
42 // \brief Moves a directory. Any files & folders inside are moved with it.
43 plResult MoveDirectory(plStringView sFromPath, plStringView sToPath);
44
45 using EnumerateFunc = plDelegate<void(const plStringBuilder& path, Type type)>;
46
47 // \brief Enumerates the files & directories under the given path
48 plResult Enumerate(plStringView sPath, EnumerateFunc callbackFunc);
49
50private:
51 DirEntry* FindDirectory(plStringBuilder& path);
52
53private:
54 DirEntry m_TopLevelDir;
55 plString m_sTopLevelDirPath;
56};
57
58namespace
59{
60 void EnsureTrailingSlash(plStringBuilder& ref_sBuilder)
61 {
62 if (!ref_sBuilder.EndsWith("/"))
63 {
64 ref_sBuilder.Append("/");
65 }
66 }
67
68 void RemoveTrailingSlash(plStringBuilder& ref_sBuilder)
69 {
70 if (ref_sBuilder.EndsWith("/"))
71 {
72 ref_sBuilder.Shrink(0, 1);
73 }
74 }
75} // namespace
76
77template <typename T>
79
80template <typename T>
82
83template <typename T>
84plResult plFileSystemMirror<T>::AddDirectory(plStringView sPath, bool* out_pDirectoryExistsAlready)
85{
86 plStringBuilder currentDirAbsPath = sPath;
87 currentDirAbsPath.MakeCleanPath();
88 EnsureTrailingSlash(currentDirAbsPath);
89
90 if (m_sTopLevelDirPath.IsEmpty())
91 {
92 m_sTopLevelDirPath = currentDirAbsPath;
93 currentDirAbsPath.Shrink(0, 1); // remove trailing /
94
95 DirEntry* currentDir = &m_TopLevelDir;
96
98
100 files.StartSearch(currentDirAbsPath.GetData(), plFileSystemIteratorFlags::ReportFilesAndFoldersRecursive);
101 for (; files.IsValid(); files.Next())
102 {
103 const plFileStats& stats = files.GetStats();
104
105 // In case we are done with a directory, move back up
106 while (currentDirAbsPath != stats.m_sParentPath)
107 {
108 PL_ASSERT_DEV(m_dirStack.GetCount() > 0, "Unexpected file iteration order");
109 currentDir = m_dirStack.PeekBack();
110 m_dirStack.PopBack();
111 currentDirAbsPath.PathParentDirectory();
112 RemoveTrailingSlash(currentDirAbsPath);
113 }
114
115 if (stats.m_bIsDirectory)
116 {
117 m_dirStack.PushBack(currentDir);
118 plStringBuilder subdirName = stats.m_sName;
119 EnsureTrailingSlash(subdirName);
120 auto insertIt = currentDir->m_subDirectories.Insert(subdirName, DirEntry());
121 currentDir = &insertIt.Value();
122 currentDirAbsPath.AppendPath(stats.m_sName);
123 }
124 else
125 {
126 currentDir->m_files.Insert(std::move(stats.m_sName), T{});
127 }
128 }
129 if (out_pDirectoryExistsAlready != nullptr)
130 {
131 *out_pDirectoryExistsAlready = false;
132 }
133 }
134 else
135 {
136 DirEntry* parentDir = FindDirectory(currentDirAbsPath);
137 if (parentDir == nullptr)
138 {
139 return PL_FAILURE;
140 }
141
142 if (out_pDirectoryExistsAlready != nullptr)
143 {
144 *out_pDirectoryExistsAlready = currentDirAbsPath.IsEmpty();
145 }
146
147 while (!currentDirAbsPath.IsEmpty())
148 {
149 const char* dirEnd = currentDirAbsPath.FindSubString("/");
150 plStringView subdirName(currentDirAbsPath.GetData(), dirEnd + 1);
151 auto insertIt = parentDir->m_subDirectories.Insert(subdirName, DirEntry());
152 parentDir = &insertIt.Value();
153 currentDirAbsPath.Shrink(plStringUtils::GetCharacterCount(subdirName.GetStartPointer(), subdirName.GetEndPointer()), 0);
154 }
155 }
156
157 return PL_SUCCESS;
158}
159
160template <typename T>
161plResult plFileSystemMirror<T>::AddFile(plStringView sPath0, const T& value, bool* out_pFileExistsAlready, T* out_pOldValue)
162{
163 plStringBuilder sPath = sPath0;
164 DirEntry* dir = FindDirectory(sPath);
165 if (dir == nullptr)
166 {
167 return PL_FAILURE; // file not under top level directory
168 }
169
170 const char* szSlashPos = sPath.FindSubString("/");
171
172 while (szSlashPos != nullptr)
173 {
174 plStringView subdirName(sPath.GetData(), szSlashPos + 1);
175 auto insertIt = dir->m_subDirectories.Insert(subdirName, DirEntry());
176 dir = &insertIt.Value();
177 sPath.Shrink(plStringUtils::GetCharacterCount(subdirName.GetStartPointer(), subdirName.GetEndPointer()), 0);
178 szSlashPos = sPath.FindSubString("/");
179 }
180
181 auto it = dir->m_files.Find(sPath);
182 // Do not add the file twice
183 if (!it.IsValid())
184 {
185 dir->m_files.Insert(sPath, value);
186 if (out_pFileExistsAlready != nullptr)
187 {
188 *out_pFileExistsAlready = false;
189 }
190 }
191 else
192 {
193 if (out_pFileExistsAlready != nullptr)
194 {
195 *out_pFileExistsAlready = true;
196 }
197 if (out_pOldValue != nullptr)
198 {
199 *out_pOldValue = it.Value();
200 }
201 it.Value() = value;
202 }
203 return PL_SUCCESS;
204}
205
206template <typename T>
208{
209 plStringBuilder sPath = sPath0;
210 DirEntry* dir = FindDirectory(sPath);
211 if (dir == nullptr)
212 {
213 return PL_FAILURE; // file not under top level directory
214 }
215
216 if (sPath.FindSubString("/") != nullptr)
217 {
218 return PL_FAILURE; // file does not exist
219 }
220
221 if (dir->m_files.GetCount() == 0)
222 {
223 return PL_FAILURE; // there are no files in this directory
224 }
225
226 auto it = dir->m_files.Find(sPath);
227 if (!it.IsValid())
228 {
229 return PL_FAILURE; // file does not exist
230 }
231
232 dir->m_files.Remove(it);
233 return PL_SUCCESS;
234}
235
236template <typename T>
238{
239 plStringBuilder parentPath = sPath;
240 plStringBuilder dirName = sPath;
241 parentPath.PathParentDirectory();
242 EnsureTrailingSlash(parentPath);
243 dirName.Shrink(parentPath.GetCharacterCount(), 0);
244 EnsureTrailingSlash(dirName);
245
246 DirEntry* parentDir = FindDirectory(parentPath);
247 if (parentDir == nullptr || !parentPath.IsEmpty())
248 {
249 return PL_FAILURE;
250 }
251
252 if (!parentDir->m_subDirectories.Remove(dirName))
253 {
254 return PL_FAILURE;
255 }
256
257 return PL_SUCCESS;
258}
259
260template <typename T>
262{
263 plStringBuilder sFromPath = sFromPath0;
264 plStringBuilder sFromName = sFromPath0;
265 sFromPath.PathParentDirectory();
266 EnsureTrailingSlash(sFromPath);
267 sFromName.Shrink(sFromPath.GetCharacterCount(), 0);
268 EnsureTrailingSlash(sFromName);
269
270
271 plStringBuilder sToPath = sToPath0;
272 plStringBuilder sToName = sToPath0;
273 sToPath.PathParentDirectory();
274 EnsureTrailingSlash(sToPath);
275 sToName.Shrink(sToPath.GetCharacterCount(), 0);
276 EnsureTrailingSlash(sToName);
277
278 DirEntry* moveFromDir = FindDirectory(sFromPath);
279 if (!moveFromDir)
280 {
281 return PL_FAILURE;
282 }
283 PL_ASSERT_DEV(sFromPath.IsEmpty(), "move from directory should fully exist");
284
285 DirEntry* moveToDir = FindDirectory(sToPath);
286 if (!moveToDir)
287 {
288 return PL_FAILURE;
289 }
290
291 if (!sToPath.IsEmpty())
292 {
293 do
294 {
295 const char* dirEnd = sToPath.FindSubString("/");
296 plStringView subdirName(sToPath.GetData(), dirEnd + 1);
297 auto insertIt = moveToDir->m_subDirectories.Insert(subdirName, DirEntry());
298 moveToDir = &insertIt.Value();
299 sToPath.Shrink(0, plStringUtils::GetCharacterCount(subdirName.GetStartPointer(), subdirName.GetEndPointer()));
300 } while (!sToPath.IsEmpty());
301 }
302
303 DirEntry movedDir;
304 {
305 auto fromIt = moveFromDir->m_subDirectories.Find(sFromName);
306 if (!fromIt.IsValid())
307 {
308 return PL_FAILURE;
309 }
310
311 movedDir = std::move(fromIt.Value());
312 moveFromDir->m_subDirectories.Remove(fromIt);
313 }
314
315 moveToDir->m_subDirectories.Insert(sToName, std::move(movedDir));
316
317 return PL_SUCCESS;
318}
319
320namespace
321{
322 template <typename T>
323 struct plDirEnumerateState
324 {
327 };
328} // namespace
329
330template <typename T>
331plResult plFileSystemMirror<T>::Enumerate(plStringView sPath0, EnumerateFunc callbackFunc)
332{
334 plStringBuilder sPath = sPath0;
335 if (!sPath.EndsWith("/"))
336 {
337 sPath.Append("/");
338 }
339 DirEntry* dirToEnumerate = FindDirectory(sPath);
340 if (dirToEnumerate == nullptr)
341 {
342 return PL_FAILURE;
343 }
344 if (!sPath.IsEmpty())
345 {
346 return PL_FAILURE; // requested folder to enumerate doesn't exist
347 }
348 DirEntry* currentDir = dirToEnumerate;
349 typename plMap<plString, plFileSystemMirror::DirEntry>::Iterator currentSubDirIt = currentDir->m_subDirectories.GetIterator();
350 sPath = sPath0;
351
352 while (currentDir != nullptr)
353 {
354 if (currentSubDirIt.IsValid())
355 {
356 DirEntry* nextDir = &currentSubDirIt.Value();
357 sPath.AppendPath(currentSubDirIt.Key());
358 currentSubDirIt.Next();
359 dirStack.PushBack({currentDir, currentSubDirIt});
360 currentDir = nextDir;
361 }
362 else
363 {
364 plStringBuilder sFilePath;
365 for (auto& file : currentDir->m_files)
366 {
367 sFilePath = sPath;
368 sFilePath.AppendPath(file.Key());
369 callbackFunc(sFilePath, Type::File);
370 }
371
372 if (currentDir != dirToEnumerate)
373 {
374 if (sPath.EndsWith("/") && sPath.GetElementCount() > 1)
375 {
376 sPath.Shrink(0, 1);
377 }
378 callbackFunc(sPath, Type::Directory);
379 }
380
381 if (dirStack.IsEmpty())
382 {
383 currentDir = nullptr;
384 }
385 else
386 {
387 currentDir = dirStack.PeekBack().dir;
388 currentSubDirIt = dirStack.PeekBack().subDirIt;
389 dirStack.PopBack();
390 sPath.PathParentDirectory();
391 if (sPath.GetElementCount() > 1 && sPath.EndsWith("/"))
392 {
393 sPath.Shrink(0, 1);
394 }
395 }
396 }
397 }
398
399 return PL_SUCCESS;
400}
401
402template <typename T>
404{
405 if (!path.StartsWith(m_sTopLevelDirPath))
406 {
407 return nullptr;
408 }
409 path.TrimWordStart(m_sTopLevelDirPath);
410
411 DirEntry* currentDir = &m_TopLevelDir;
412
413 bool found = false;
414 do
415 {
416 found = false;
417 for (auto& dir : currentDir->m_subDirectories)
418 {
419 if (path.StartsWith(dir.Key()))
420 {
421 currentDir = &dir.Value();
422 path.TrimWordStart(dir.Key());
423 path.TrimWordStart("/");
424 found = true;
425 break;
426 }
427 }
428 } while (found);
429
430 return currentDir;
431}
void PushBack(const T &value)
Pushes value at the end of the array.
Definition ArrayBase_inl.h:333
void PopBack(plUInt32 uiCountToRemove=1)
Removes count elements from the end of the array.
Definition ArrayBase_inl.h:379
T & PeekBack()
Returns the last element of the array.
Definition ArrayBase_inl.h:388
bool IsEmpty() const
Returns true, if the array does not contain any elements.
Definition ArrayBase_inl.h:178
plUInt32 GetCount() const
Returns the number of active elements in the array.
Definition ArrayBase_inl.h:172
An plFileSystemIterator allows to iterate over all files in a certain directory.
Definition OSFile.h:94
bool IsValid() const
Returns true if the iterator currently points to a valid file entry.
void StartSearch(plStringView sSearchTerm, plBitflags< plFileSystemIteratorFlags > flags=plFileSystemIteratorFlags::Default)
Starts a search at the given folder. Use * and ? as wildcards.
void Next()
Advances the iterator to the next file object. Might recurse into sub-folders.
const plFileStats & GetStats() const
Returns the file stats of the current object that the iterator points to.
Definition OSFile.h:130
Definition FileSystemMirror.h:13
A hybrid array uses in-place storage to handle the first few elements without any allocation....
Definition HybridArray.h:12
Definition Map.h:408
plStringBuilder is a class that is meant for creating and modifying strings.
Definition StringBuilder.h:35
bool TrimWordStart(plStringView sWord)
If the string starts with the given word (case insensitive), it is removed and the function returns t...
Definition StringBuilder.cpp:1202
const char * GetData() const
Returns a char pointer to the internal Utf8 data.
Definition StringBuilder_inl.h:148
plUInt32 GetElementCount() const
Returns the number of bytes that this string takes up.
Definition StringBuilder_inl.h:78
void Append(plUInt32 uiChar)
Appends a single Utf32 character.
Definition StringBuilder_inl.h:94
void PathParentDirectory(plUInt32 uiLevelsUp=1)
Modifies this string to point to the parent directory.
Definition StringBuilder.cpp:856
void Shrink(plUInt32 uiShrinkCharsFront, plUInt32 uiShrinkCharsBack)
Removes the first n and last m characters from this string.
Definition StringBuilder.cpp:393
plUInt32 GetCharacterCount() const
Returns the number of characters of which this string consists. Might be less than GetElementCount,...
Definition StringBuilder_inl.h:83
void MakeCleanPath()
Removes "../" where possible, replaces all path separators with /, removes double slashes.
Definition StringBuilder.cpp:751
void AppendPath(plStringView sPath1, plStringView sPath2={}, plStringView sPath3={}, plStringView sPath4={})
Appends several path pieces. Makes sure they are always properly separated by a slash.
Definition StringBuilder.cpp:866
static plUInt32 GetCharacterCount(const char *szUtf8, const char *pStringEnd=plUnicodeUtils::GetMaxStringEnd< char >())
Returns the number of characters (not Bytes!) in a Utf8 string (excluding the zero terminator),...
Definition StringUtils_inl.h:81
plStringView represent a read-only sub-string of a larger string, as it can store a dedicated string ...
Definition StringView.h:34
A generic delegate class which supports static functions and member functions.
Definition Delegate.h:76
Holds the stats for a file.
Definition OSFile.h:34
bool m_bIsDirectory
Whether the file object is a file or folder.
Definition OSFile.h:56
plString m_sName
The name of the file or folder that the stats are for. Does not include the parent path to it....
Definition OSFile.h:47
plStringBuilder m_sParentPath
Path to the parent folder. Append m_sName to m_sParentPath to obtain the full path.
Definition OSFile.h:43
Definition FileSystemMirror.h:22
PL_ALWAYS_INLINE bool IsValid() const
Checks whether this iterator points to a valid element.
Definition Map.h:27
PL_FORCE_INLINE const KeyType & Key() const
Returns the 'key' of the element that this iterator points to.
Definition Map.h:34
void Next()
Advances the iterator to the next element in the map. The iterator will not be valid anymore,...
Definition Map_inl.h:58
Forward Iterator to iterate over all elements in sorted order.
Definition Map.h:103
PL_FORCE_INLINE ValueType & Value()
Returns the 'value' of the element that this iterator points to.
Definition Map.h:119
Default enum for returning failure or success, instead of using a bool.
Definition Types.h:54
bool StartsWith(plStringView sStartsWith) const
Returns true, if this string starts with the given string.
Definition StringBase_inl.h:31
bool IsEmpty() const
Returns whether the string is an empty string.
Definition StringBase_inl.h:25
bool EndsWith(plStringView sEndsWith) const
Returns true, if this string ends with the given string.
Definition StringBase_inl.h:43
const char * FindSubString(plStringView sStringToFind, const char *szStartSearchAt=nullptr) const
Definition StringBase_inl.h:55