载入中...
搜索中...
未找到
Filesystem.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <iostream>
5#include <memory>
6#include <mutex>
7#include <sstream>
8#include <string>
9
11#include "common/b64.h"
12#include "common/utf8.h"
13#include "common/Exception.h"
14#include "common/Diagnostic.h"
15#include "cmdline/cmdline.h"
16
17// PhysFS
18#include "physfs/physfs.h"
19
20#ifdef EVENGINE_WINDOWS
21#include <direct.h>
22#include <windows.h>
23#else
24#include <sys/param.h>
25#include <unistd.h>
26#endif
27
28#ifdef EVENGINE_IOS
29#include "ios/ios.h"
30#endif
31
32#ifdef EVENGINE_WEBGPU
33#include "webgpu/webplatform.h"
34#endif
35
36#include <filesystem>
37#include <string>
38
39#ifdef EVENGINE_ANDROID
40#include <SDL2/SDL.h>
41
42#include "android/android.h"
43#endif
44
45using namespace std;
46
47namespace {
48size_t getDriveDelim(const std::string &input) {
49 for (size_t i = 0; i < input.size(); ++i)
50 if (input[i] == '/' || input[i] == '\\') return i;
51 // Something's horribly wrong
52 return 0;
53}
54
55std::string getDriveRoot(const std::string &input) { return input.substr(0, getDriveDelim(input) + 1); }
56
57std::string skipDriveRoot(const std::string &input) { return input.substr(getDriveDelim(input) + 1); }
58
59std::string normalize(const std::string &input) {
60 std::stringstream out;
61
62 bool seenSep = false, isSep = false;
63 for (size_t i = 0; i < input.size(); ++i) {
64 isSep = (input[i] == EVENGINE_PATH_SEPARATOR[0]);
65 if (!isSep || !seenSep) out << input[i];
66 seenSep = isSep;
67 }
68
69 return out.str();
70}
71
72} // namespace
73
74namespace eve {
75namespace filesystem {
76namespace physfs {
77
78Filesystem::Filesystem() : fused(false), fusedSet(false) {
79 requirePath = {"?.lua", "?/init.lua"};
80 cRequirePath = {"??"};
81 if (auto* c = getModInst(eve::cmd,Cmdline)) {
82 // unit_test / embedders never call Cmdline::runArgs: argv stays empty.
83 const std::string a0 = c->getArgv(0);
84 if (a0.empty()) init(NULL);
85 else init(a0.c_str());
86 } else {
87 init(NULL);
88 }
89}
90
92 unwatchAll();
93
94 if (memoryArchive_) {
95 free(memoryArchive_);
96 memoryArchive_ = nullptr;
97 }
98
99#ifdef EVENGINE_ANDROID
100 android::deinitializeVirtualArchive();
101#endif
102
103 if (PHYSFS_isInit()) PHYSFS_deinit();
104}
105
106void Filesystem::init(const char* arg0) {
107 if (!PHYSFS_init(arg0))
108 throw Exception("Failed to initialize filesystem: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
109
110 // Enable symlinks by default.
111 setSymlinksEnabled(true);
112}
113
115 if (fusedSet) return;
116 this->fused = value;
117 fusedSet = true;
118}
119
121 if (!fusedSet) return false;
122 return fused;
123}
124
125bool Filesystem::setIdentity(std::string ident, bool appendToPath) {
126 if (!PHYSFS_isInit()) return false;
127
128 std::string old_save_path = save_path_full;
129
130 // Store the save directory.
131 save_identity = std::string(ident);
132
133 // Generate the relative path to the game save folder.
134 save_path_relative = std::string(EVENGINE_APPDATA_PREFIX EVENGINE_APPDATA_FOLDER EVENGINE_PATH_SEPARATOR) + save_identity;
135
136 // Generate the full path to the game save folder.
137 save_path_full = std::string(getAppdataDirectory()) + std::string(EVENGINE_PATH_SEPARATOR);
138 if (fused)
139 save_path_full += std::string(EVENGINE_APPDATA_PREFIX) + save_identity;
140 else
141 save_path_full += save_path_relative;
142
143 save_path_full = normalize(save_path_full);
144
145#ifdef EVENGINE_ANDROID
146 if (save_identity == "") save_identity = "unnamed";
147
148 std::string storage_path;
150 storage_path = SDL_AndroidGetExternalStoragePath();
151 else
152 storage_path = SDL_AndroidGetInternalStoragePath();
153
154 std::string save_directory = storage_path + "/save";
155
156 save_path_full = storage_path + std::string("/save/") + save_identity;
157
158 if (!android::directoryExists(save_path_full.c_str()) && !android::mkdir(save_path_full.c_str()))
159 SDL_Log("Error: Could not create save directory %s!", save_path_full.c_str());
160#endif
161
162 // We now have something like:
163 // save_identity: game
164 // save_path_relative: ./LOVE/game
165 // save_path_full: C:\Documents and Settings\user\Application Data/LOVE/game
166
167 // We don't want old read-only save paths to accumulate when we set a new
168 // identity.
169 if (!old_save_path.empty()) PHYSFS_unmount(old_save_path.c_str());
170
171 // Try to add the save directory to the search path.
172 // (No error on fail, it means that the path doesn't exist).
173 PHYSFS_mount(save_path_full.c_str(), nullptr, appendToPath);
174
175 // HACK: This forces setupWriteDirectory to be called the next time a file
176 // is opened for writing - otherwise it won't be called at all if it was
177 // already called at least once before.
178 PHYSFS_setWriteDir(nullptr);
179
180 return true;
181}
182
183std::string Filesystem::getIdentity() const { return save_identity.c_str(); }
184
185bool Filesystem::setSource(std::string source) {
186 if (!PHYSFS_isInit()) return false;
187
188 // Check whether directory is already set.
189 if (!game_source.empty()) return false;
190
191 std::string new_search_path = source;
192
193#ifdef EVENGINE_ANDROID
194 if (!android::createStorageDirectories()) SDL_Log("Error creating storage directories!");
195
196 new_search_path = "";
197
198 PHYSFS_Io *gameLoveIO;
199 bool hasFusedGame = android::checkFusedGame((void **)&gameLoveIO);
200
201 if (hasFusedGame) {
202 if (gameLoveIO) {
203 // Actually we should just be able to mount gameLoveIO, but that's experimental.
204 gameLoveIO->destroy(gameLoveIO);
205 goto oldschool;
206 } else {
207 if (!android::initializeVirtualArchive()) {
208 SDL_Log("Unable to mount AAsset: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
209 return false;
210 }
211 }
212 } else {
213 oldschool:
214 new_search_path = android::getSelectedGameFile();
215
216 // try mounting first, if that fails, load to memory and mount
217 if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1)) {
218 // PHYSFS cannot yet mount a zip file inside an .apk
219 SDL_Log("Mounting %s did not work. Loading to memory.", new_search_path.c_str());
220 char * game_archive_ptr = NULL;
221 size_t game_archive_size = 0;
222 if (!android::loadGameArchiveToMemory(new_search_path.c_str(), &game_archive_ptr,
223 &game_archive_size)) {
224 SDL_Log("Failure memory loading archive %s", new_search_path.c_str());
225 return false;
226 }
227 if (!PHYSFS_mountMemory(game_archive_ptr, game_archive_size, android::freeGameArchiveMemory,
228 "archive.zip", "/", 0)) {
229 SDL_Log("Failure mounting in-memory archive.");
230 android::freeGameArchiveMemory(game_archive_ptr);
231 return false;
232 }
233 }
234 }
235#else
236 // Add the directory.
237 if (!PHYSFS_mount(new_search_path.c_str(), nullptr, 1)) return false;
238#endif
239
240 // Save the game source.
241 game_source = new_search_path;
242
243 return true;
244}
245
246std::string Filesystem::getSource() const { return game_source.c_str(); }
247
248bool Filesystem::setSourceFromMemory(const void* data, size_t size) {
249 if (!PHYSFS_isInit() || !data || size == 0) return false;
250 // Only one source per process.
251 if (!game_source.empty()) return false;
252
253 // PhysFS keeps a pointer to the buffer for the lifetime of the mount, so we
254 // must own a copy that outlives it. Freed in the destructor.
255 void* owned = malloc(size);
256 if (!owned) return false;
257 memcpy(owned, data, size);
258
259 // Mount at "/" so dofile("config.nut") / "main.nut" resolve via PhysFS.
260 if (!PHYSFS_mountMemory(owned, size, nullptr, "game.eve", "/", 1)) {
261 const auto err = PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode());
262 std::cerr << "PhysFS mountMemory failed: " << (err ? err : "unknown error") << std::endl;
263 free(owned);
264 return false;
265 }
266
267 memoryArchive_ = owned;
268 game_source = "game.eve";
269 return true;
270}
271
273 if (!PHYSFS_isInit()) return false;
274
275 // These must have one be set.
276 if (save_identity.empty() || save_path_full.empty() || save_path_relative.empty()) return false;
277
278 // We need to make sure the write directory is created. To do that, we also
279 // need to make sure all its parent directories are also created.
280 std::string temp_writedir = getDriveRoot(save_path_full);
281 std::string temp_createdir = skipDriveRoot(save_path_full);
282
283 // On some sandboxed platforms, physfs will break when its write directory
284 // is the root of the drive and it tries to create a folder (even if the
285 // folder's path is in a writable location.) If the user's home folder is
286 // in the save path, we'll try starting from there instead.
287 if (save_path_full.find(getUserDirectory()) == 0) {
288 temp_writedir = getUserDirectory();
289 temp_createdir = save_path_full.substr(getUserDirectory().length());
290
291 // Strip leading '/' characters from the path we want to create.
292 size_t startpos = temp_createdir.find_first_not_of('/');
293 if (startpos != std::string::npos) temp_createdir = temp_createdir.substr(startpos);
294 }
295
296 // Set either '/' or the user's home as a writable directory.
297 // (We must create the save folder before mounting it).
298 if (!PHYSFS_setWriteDir(temp_writedir.c_str())) return false;
299
300 // Create the save folder. (We're now "at" either '/' or the user's home).
301 if (!createDirectory(temp_createdir.c_str())) {
302 // Clear the write directory in case of error.
303 PHYSFS_setWriteDir(nullptr);
304 return false;
305 }
306
307 // Set the final write directory.
308 if (!PHYSFS_setWriteDir(save_path_full.c_str())) return false;
309
310 // Add the directory. (Will not be readded if already present).
311 if (!PHYSFS_mount(save_path_full.c_str(), nullptr, 0)) {
312 PHYSFS_setWriteDir(nullptr); // Clear the write directory in case of error.
313 return false;
314 }
315
316 return true;
317}
318
319bool Filesystem::mount(std::string archive, std::string mountpoint, bool appendToPath) {
320 if (!PHYSFS_isInit() || archive.empty()) return false;
321
322 std::string realPath;
323 std::string sourceBase = getSourceBaseDirectory();
324
325 // Check whether the given archive path is in the list of allowed full paths.
326 auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
327
328 if (it != allowedMountPaths.end())
329 realPath = *it;
330 else if (isFused() && sourceBase.compare(archive) == 0) {
331 // Special case: if the game is fused and the archive is the source's
332 // base directory, mount it even though it's outside of the save dir.
333 realPath = sourceBase;
334 } else {
335 // Not allowed for safety reasons.
336 if (archive.size() == 0 || archive.find("..") != string::npos || archive == "/") return false;
337
338 const char* realDir = PHYSFS_getRealDir(archive.c_str());
339 if (!realDir) return false;
340
341 realPath = realDir;
342
343 // Always disallow mounting of files inside the game source, since it
344 // won't work anyway if the game source is a zipped .love file.
345 if (realPath.find(game_source) == 0) return false;
346
347 realPath += EVENGINE_PATH_SEPARATOR;
348 realPath += archive;
349 }
350
351 if (realPath.length() == 0) return false;
352
353 return PHYSFS_mount(realPath.c_str(), mountpoint.c_str(), appendToPath) != 0;
354}
355
356bool Filesystem::mount(Data *data, std::string archivename, std::string mountpoint, bool appendToPath) {
357 if (!PHYSFS_isInit()) return false;
358
359 if (PHYSFS_mountMemory(data->getData(), data->getSize(), nullptr, archivename.c_str(), mountpoint.c_str(), appendToPath) != 0) {
360 mountedData[archivename] = data;
361 return true;
362 }
363
364 return false;
365}
366
367bool Filesystem::mountRealDirectory(std::string realDir, std::string mountpoint, bool appendToPath) {
368 if (!PHYSFS_isInit() || realDir.empty()) return false;
369 if (!isRealDirectory(realDir)) return false;
370 if (mountpoint.empty()) mountpoint = "/";
371
372 // Track the dir so unmountRealDirectory() / the destructor can clean it up,
373 // even though PhysFS itself needs the OS path string to unmount.
374 std::lock_guard<std::mutex> lock(mountMu_);
375 if (std::find(mountedRealDirs_.begin(), mountedRealDirs_.end(), realDir) == mountedRealDirs_.end()) {
376 if (!PHYSFS_mount(realDir.c_str(), mountpoint.c_str(), appendToPath ? 1 : 0)) return false;
377 mountedRealDirs_.push_back(realDir);
378 }
379 return true;
380}
381
382bool Filesystem::unmountRealDirectory(std::string realDir) {
383 std::lock_guard<std::mutex> lock(mountMu_);
384 auto it = std::find(mountedRealDirs_.begin(), mountedRealDirs_.end(), realDir);
385 if (it == mountedRealDirs_.end()) return false;
386 const bool ok = PHYSFS_unmount(realDir.c_str()) != 0;
387 mountedRealDirs_.erase(it);
388 return ok;
389}
390
391bool Filesystem::unmount(std::string archive) {
392 if (!PHYSFS_isInit() || archive.empty()) return false;
393
394 auto datait = mountedData.find(archive);
395
396 if (datait != mountedData.end() && PHYSFS_unmount(archive.c_str()) != 0) {
397 mountedData.erase(datait);
398 return true;
399 }
400
401 std::string realPath;
402 std::string sourceBase = getSourceBaseDirectory();
403
404 // Check whether the given archive path is in the list of allowed full paths.
405 auto it = std::find(allowedMountPaths.begin(), allowedMountPaths.end(), archive);
406
407 if (it != allowedMountPaths.end())
408 realPath = *it;
409 else if (isFused() && sourceBase.compare(archive) == 0) {
410 // Special case: if the game is fused and the archive is the source's
411 // base directory, unmount it even though it's outside of the save dir.
412 realPath = sourceBase;
413 } else {
414 // Not allowed for safety reasons.
415 if (archive.size() == 0 || archive.find("..") != string::npos || archive == "/") return false;
416
417 const char* realDir = PHYSFS_getRealDir(archive.c_str());
418 if (!realDir) return false;
419
420 realPath = realDir;
421 realPath += EVENGINE_PATH_SEPARATOR;
422 realPath += archive;
423 }
424
425 const char* mountPoint = PHYSFS_getMountPoint(realPath.c_str());
426 if (!mountPoint) return false;
427
428 return PHYSFS_unmount(realPath.c_str()) != 0;
429}
430
432 for (const auto &datapair : mountedData) {
433 if (datapair.second == data) {
434 std::string archive = datapair.first;
435 return unmount(archive.c_str());
436 }
437 }
438
439 return false;
440}
441
442filesystem::File *Filesystem::newFile(std::string filename) const { return new File(filename); }
443
445 if (cwd.empty()) {
446#ifdef EVENGINE_WINDOWS
447
448 WCHAR w_cwd[EVENGINE_MAX_PATH];
449 _wgetcwd(w_cwd, EVENGINE_MAX_PATH);
450 cwd = to_utf8(w_cwd);
451 replace_char(cwd, '\\', '/');
452#else
453 char *cwd_char = new char[EVENGINE_MAX_PATH];
454
455 if (getcwd(cwd_char, EVENGINE_MAX_PATH))
456 cwd = cwd_char; // if getcwd fails, cwd_char (and thus cwd) will still be empty
457
458 delete[] cwd_char;
459#endif
460 }
461
462 return cwd.c_str();
463}
464
466#ifdef EVENGINE_IOS
467 // PHYSFS_getUserDir doesn't give exactly the path we want on iOS.
468 static std::string userDir = normalize(ios::getHomeDirectory());
469#elif defined(EVENGINE_WEBGPU)
470 static std::string userDir = normalize(eve::webgpu_platform::getHomeDirectory());
471#else
472 // PhysFS deprecated PHYSFS_getUserDir() in 2.1 without a non-deprecated
473 // home-directory replacement (PHYSFS_getPrefDir() is app-specific), so the
474 // public API is kept for the home-directory semantics.
475#if defined(__GNUC__)
476#pragma GCC diagnostic push
477#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
478#endif
479 const char* dir = PHYSFS_getUserDir();
480#if defined(__GNUC__)
481#pragma GCC diagnostic pop
482#endif
483 static std::string userDir;
484 if (dir) userDir = normalize(dir);
485#endif
486 return userDir;
487}
488
490 if (appdata.empty()) {
491#ifdef EVENGINE_WINDOWS_UWP
492 appdata = getUserDirectory();
493#elif defined(EVENGINE_WEBGPU)
494 appdata = normalize(eve::webgpu_platform::getAppdataDirectory());
495#elif defined(EVENGINE_WINDOWS)
496 wchar_t *w_appdata = _wgetenv(L"APPDATA");
497 appdata = to_utf8(w_appdata);
498 replace_char(appdata, '\\', '/');
499#elif defined(EVENGINE_MACOSX)
500 std::string udir = getUserDirectory();
501 udir.append("/Library/Application Support");
502 appdata = normalize(udir);
503#elif defined(EVENGINE_IOS)
504 appdata = normalize(ios::getAppdataDirectory());
505#elif defined(EVENGINE_LINUX)
506 char *xdgdatahome = getenv("XDG_DATA_HOME");
507 if (!xdgdatahome)
508 appdata = normalize(std::string(getUserDirectory()) + "/.local/share/");
509 else
510 appdata = xdgdatahome;
511#else
512 appdata = getUserDirectory();
513#endif
514 }
515 return appdata;
516}
517
518std::string Filesystem::getSaveDirectory() { return save_path_full.c_str(); }
519
521 size_t source_len = game_source.length();
522
523 if (source_len == 0) return "";
524
525 // FIXME: This doesn't take into account parent and current directory
526 // symbols (i.e. '..' and '.')
527#ifdef EVENGINE_WINDOWS
528 // In windows, delimiters can be either '/' or '\'.
529 size_t base_end_pos = game_source.find_last_of("/\\", source_len - 2);
530#else
531 size_t base_end_pos = game_source.find_last_of('/', source_len - 2);
532#endif
533
534 if (base_end_pos == std::string::npos) return "";
535
536 // If the source is in the unix root (aka '/'), we want to keep the '/'.
537 if (base_end_pos == 0) base_end_pos = 1;
538
539 return game_source.substr(0, base_end_pos);
540}
541
542std::string Filesystem::getRealDirectory(std::string filename) const {
543 if (!PHYSFS_isInit()) throw Exception("PhysFS is not initialized.");
544
545 const char* dir = PHYSFS_getRealDir(filename.c_str());
546
547 if (dir == nullptr) throw Exception("File does not exist on disk.");
548
549 return std::string(dir);
550}
551
552bool Filesystem::getInfo(std::string filepath, Info &info) const {
553 if (!PHYSFS_isInit()) return false;
554
555 PHYSFS_Stat stat = {};
556 if (!PHYSFS_stat(filepath.c_str(), &stat)) return false;
557
558 info.size = (int64_t)stat.filesize;
559 info.modtime = (int64_t)stat.modtime;
560
561 if (stat.filetype == PHYSFS_FILETYPE_REGULAR)
562 info.type = "file";
563 else if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY)
564 info.type = "directory";
565 else if (stat.filetype == PHYSFS_FILETYPE_SYMLINK)
566 info.type = "symlink";
567 else
568 info.type = "other";
569
570 return true;
571}
572
574 if (!PHYSFS_isInit()) return false;
575
576 if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) return false;
577
578 if (!PHYSFS_mkdir(dir.c_str())) return false;
579
580 return true;
581}
582
583bool Filesystem::remove(std::string file) {
584 if (!PHYSFS_isInit()) return false;
585
586 if (PHYSFS_getWriteDir() == 0 && !setupWriteDirectory()) return false;
587
588 if (!PHYSFS_delete(file.c_str())) return false;
589
590 return true;
591}
592
593FileData *Filesystem::read(std::string filename, int64_t size) const {
594 physfs::File file(filename);
595
596 file.open("rb");
597
598 // close() is called in the File destructor.
599 return file.read(size);
600}
601
602void Filesystem::write(std::string filename, const void *data, int64_t size) const {
603 physfs::File file(filename);
604
605 file.open("wb");
606
607 // close() is called in the File destructor.
608 if (!file.write(data, size)) throw eve::Exception("Data could not be written.");
609}
610
611void Filesystem::append(std::string filename, const void *data, int64_t size) const {
612 physfs::File file(filename);
613
614 file.open("ab");
615
616 // close() is called in the File destructor.
617 if (!file.write(data, size)) throw eve::Exception("Data could not be written.");
618}
619
620std::vector<std::string> Filesystem::getDirectoryItems(std::string dir) {
621 std::vector<std::string> items;
622 if (!PHYSFS_isInit()) return items;
623
624 char **rc = PHYSFS_enumerateFiles(dir.c_str());
625
626 if (rc == nullptr) return items;
627
628 for (char **i = rc; *i != 0; i++) items.push_back(*i);
629
630 PHYSFS_freeList(rc);
631 return items;
632}
633
635 if (!PHYSFS_isInit()) return;
636
637 PHYSFS_permitSymbolicLinks(enable ? 1 : 0);
638}
639
641 if (!PHYSFS_isInit()) return false;
642
643 return PHYSFS_symbolicLinksPermitted() != 0;
644}
645
646std::vector<std::string> &Filesystem::getRequirePath() { return requirePath; }
647
648std::vector<std::string> &Filesystem::getCRequirePath() { return cRequirePath; }
649
650void Filesystem::allowMountingForPath(const std::string &path) {
651 if (std::find(allowedMountPaths.begin(), allowedMountPaths.end(), path) == allowedMountPaths.end())
652 allowedMountPaths.push_back(path);
653}
654
655namespace {
656
657bool looksAbsolute(const std::string &path) {
658 if (path.empty()) return false;
659 if (path[0] == '/' || path[0] == '\\') return true;
660#ifdef EVENGINE_WINDOWS
661 if (path.size() >= 2 && path[1] == ':') return true;
662#endif
663 return false;
664}
665
666std::string joinPath(const std::string &a, const std::string &b) {
667 if (a.empty()) return b;
668 if (b.empty()) return a;
669 char last = a.back();
670 if (last == '/' || last == '\\') return a + b;
671 return a + "/" + b;
672}
673
674std::string parentDir(const std::string &path) {
675 auto pos = path.find_last_of("/\\");
676 if (pos == std::string::npos) return ".";
677 if (pos == 0) return "/";
678 return path.substr(0, pos);
679}
680
681std::string baseName(const std::string &path) {
682 auto pos = path.find_last_of("/\\");
683 if (pos == std::string::npos) return path;
684 return path.substr(pos + 1);
685}
686
687bool hasParentSegment(const std::string &path) {
688 if (path == "..") return true;
689 if (path.size() >= 3 && (path.compare(0, 3, "../") == 0 || path.compare(0, 3, "..\\") == 0))
690 return true;
691 return path.find("/..") != std::string::npos || path.find("\\..") != std::string::npos;
692}
693
694std::filesystem::path pathFromUtf8(const std::string &text) {
695 const auto *data = reinterpret_cast<const char8_t *>(text.data());
696 return std::filesystem::path(std::u8string_view(data, text.size()));
697}
698
699std::string pathToUtf8(const std::filesystem::path &p) {
700 auto u8 = p.u8string();
701 return std::string(reinterpret_cast<const char *>(u8.data()), u8.size());
702}
703
704} // namespace
705
706FileWatch &Filesystem::watchers() {
707 if (!fileWatch_) fileWatch_ = std::make_unique<FileWatch>();
708 return *fileWatch_;
709}
710
711bool Filesystem::resolveWatchTarget(const std::string &path, std::string &realDir,
712 std::string &filterName, std::string &reportPath) {
713 reportPath = path;
714 filterName.clear();
715 realDir.clear();
716 if (path.empty()) return false;
717
718 // "." / "" are not reliable PhysFS paths — watch the real working directory.
719 if (path == "." || path == "./") {
720 reportPath = ".";
721 realDir = getWorkingDirectory();
722 filterName.clear();
723 return isRealDirectory(realDir);
724 }
725
726 if (looksAbsolute(path)) {
727 if (isRealDirectory(path)) {
728 realDir = path;
729 return true;
730 }
731 realDir = parentDir(path);
732 filterName = baseName(path);
733 return isRealDirectory(realDir);
734 }
735
736 // PhysFS rejects `..`. Resolve parent-relative paths against the real cwd
737 // so `fs.watch("..")` / `hot.watchTree("..")` can see a parent workflow.
738 if (hasParentSegment(path)) {
739 std::error_code ec;
740 auto candidate = std::filesystem::weakly_canonical(
741 pathFromUtf8(getWorkingDirectory()) / pathFromUtf8(path), ec);
742 if (!ec) {
743 const std::string full = pathToUtf8(candidate);
744 if (isRealDirectory(full)) {
745 realDir = full;
746 filterName.clear();
747 return true;
748 }
749 realDir = parentDir(full);
750 filterName = baseName(full);
751 return isRealDirectory(realDir);
752 }
753 }
754
755 Info info{};
756 if (getInfo(path, info)) {
757 std::string root;
758 try {
760 } catch (...) {
761 return false;
762 }
763 std::string full = joinPath(root, path);
764 if (info.type == "directory") {
765 realDir = full;
766 filterName.clear();
767 return isRealDirectory(realDir);
768 }
769 realDir = parentDir(full);
770 filterName = baseName(full);
771 return isRealDirectory(realDir);
772 }
773
774 std::string parent = parentDir(path);
775 if (parent == ".") parent.clear();
776 if (!parent.empty()) {
777 Info pinfo{};
778 if (getInfo(parent, pinfo) && pinfo.type == "directory") {
779 std::string root;
780 try {
782 } catch (...) {
783 return false;
784 }
785 realDir = joinPath(root, parent);
786 filterName = baseName(path);
787 return isRealDirectory(realDir);
788 }
789 }
790
791 // Virtual path not in VFS yet (e.g. new file): watch under cwd, not save dir.
792 {
793 std::string workingDir = getWorkingDirectory();
794 if (!workingDir.empty() && isRealDirectory(workingDir)) {
795 if (parent.empty()) {
796 realDir = workingDir;
797 filterName = baseName(path);
798 return true;
799 }
800 }
801 }
802
803 std::string save = getSaveDirectory();
804 if (!save.empty() && isRealDirectory(save)) {
805 realDir = save;
806 filterName = baseName(path);
807 return true;
808 }
809 return false;
810}
811
812bool Filesystem::watch(std::string path) {
813 std::string realDir, filter, report;
814 if (!resolveWatchTarget(path, realDir, filter, report)) return false;
815 return watchers().add(realDir, filter, report, 1);
816}
817
818eve::Result<void> Filesystem::watchRealDirectory(std::string realDir, std::string reportPath) {
819 if (realDir.empty() || reportPath.empty()) {
822 "watchRealDirectory requires a real directory and a report path"));
823 }
824 if (!isRealDirectory(realDir)) {
826 eve::DiagnosticCode::NotFound, "watchRealDirectory path is not a directory", realDir));
827 }
828 if (!watchers().add(std::move(realDir), "", std::move(reportPath), 1)) {
830 eve::DiagnosticCode::Failed, "watchRealDirectory could not add an OS watcher"));
831 }
833}
834
835bool Filesystem::unwatch(std::string path) { return watchers().remove(path); }
836
838 if (fileWatch_) fileWatch_->clear();
839}
840
842 if (!fileWatch_) return 0;
843 return fileWatch_->count();
844}
845
847 if (!fileWatch_) return "";
849 if (!fileWatch_->poll(ev)) {
850 lastWatchPath_.clear();
851 lastWatchRealPath_.clear();
852 return "";
853 }
854 lastWatchPath_ = ev.path;
855 lastWatchRealPath_ = ev.realPath;
856 return ev.kind;
857}
858
859std::string Filesystem::getLastWatchPath() const { return lastWatchPath_; }
860std::string Filesystem::getLastWatchRealPath() const { return lastWatchRealPath_; }
861
862} // namespace physfs
863} // namespace filesystem
864} // namespace eve
double value
int root
Definition AnimSmr.cpp:119
ActiveSource owned
float length
Definition CaveMesh.cpp:94
glm::vec4 p[6]
Stable, structured diagnostics shared by engine modules.
filesystem::File * file
EvpackChunkInput input
Definition Evpack.cpp:170
#define EVENGINE_MAX_PATH
Definition Filesystem.h:37
#define EVENGINE_PATH_SEPARATOR
Definition Filesystem.h:36
#define EVENGINE_APPDATA_PREFIX
Definition Filesystem.h:33
#define EVENGINE_APPDATA_FOLDER
Definition Filesystem.h:34
std::int32_t c
std::string text
std::int32_t parent
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
#define getModInst(N, T)
Definition Module.h:38
std::string path
Definition PlayHost.cpp:110
std::string filter
float size
Definition TreeMesh.cpp:156
V3 dir
Definition TreeMesh.cpp:150
const UnitySourceAsset & source
Data public API.
Definition Data.h:8
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Data buffer paired with a filename (used for type identification).
Definition FileData.h:14
bool add(const std::string &realDir, const std::string &filterName, const std::string &reportPath, int scanInterval=1)
Definition FileWatch.cpp:73
bool remove(const std::string &reportPath)
Remove by reportPath previously passed to add().
A File interface, providing generic means of reading from and writing to files.
Definition File.h:14
virtual bool isAndroidSaveExternal() const
Gets whether the Android save is external. Returns a bool.
Definition Filesystem.h:109
virtual bool isRealDirectory(const std::string &path) const
Gets whether the given full (OS-dependent) path is a directory.
File public API.
Definition File.h:17
std::string getSaveDirectory() override
Returns the save directory.
void append(std::string filename, const void *data, int64_t size) const override
Append.
bool setSource(std::string source) override
Sets the source.
int getWatchCount() const override
Returns the watch count.
bool setIdentity(std::string ident, bool appendToPath=false) override
Sets the identity.
void unwatchAll() override
Stops watching all.
std::string getRealDirectory(std::string filename) const override
Returns the real directory.
void setFused(bool value) override
Sets the fused.
bool setSourceFromMemory(const void *data, size_t size) override
Sets the source from memory.
~Filesystem() override
Filesystem.
eve::Result< void > watchRealDirectory(std::string realDir, std::string reportPath) override
Watches real directory.
bool watch(std::string path) override
Watches watch.
std::string getSource() const override
Returns the source.
filesystem::File * newFile(std::string filename) const override
Creates a file. @ownership Caller deletes unless documented otherwise.
std::string getUserDirectory() override
Returns the user directory.
bool createDirectory(std::string dir) override
Creates directory.
bool remove(std::string file) override
Removes remove.
std::vector< std::string > getDirectoryItems(std::string dir) override
Returns the directory items.
bool isFused() const override
True when fused.
bool unmount(std::string archive) override
Unmounts unmount.
std::string pollWatch() override
Polls watch.
std::string getLastWatchRealPath() const override
Returns the last watch real path.
bool mountRealDirectory(std::string realDir, std::string mountpoint, bool appendToPath=false) override
Mounts real directory.
bool unwatch(std::string path) override
Stops watching unwatch.
bool mount(std::string archive, std::string mountpoint, bool appendToPath=false) override
Mounts mount.
std::string getIdentity() const override
Returns the identity.
void init(const char *arg0) override
Initializes init.
bool setupWriteDirectory() override
Setup write directory.
std::vector< std::string > & getCRequirePath() override
Returns the c require path.
std::string getWorkingDirectory() override
Returns the working directory.
FileData * read(std::string filename, int64_t size=File::ALL) const override
Reads read.
bool unmountRealDirectory(std::string realDir) override
Unmounts real directory.
void allowMountingForPath(const std::string &path) override
Allow mounting for path.
void write(std::string filename, const void *data, int64_t size) const override
Writes write.
void setSymlinksEnabled(bool enable) override
Sets the symlinks enabled.
bool getInfo(std::string filepath, Info &info) const override
Returns the info.
bool areSymlinksEnabled() const override
Are symlinks enabled.
std::vector< std::string > & getRequirePath() override
Returns the require path.
std::string getSourceBaseDirectory() const override
Returns the source base directory.
std::string getAppdataDirectory() override
Returns the appdata directory.
std::string getLastWatchPath() const override
Returns the last watch path.
Vec2 normalize(const Vec2 &a)
Normalize.
Definition UrbanTypes.h:44
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
glm::uvec4 info