summaryrefslogtreecommitdiff
path: root/deps/g3dlite/source/FileSystem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'deps/g3dlite/source/FileSystem.cpp')
-rw-r--r--deps/g3dlite/source/FileSystem.cpp874
1 files changed, 874 insertions, 0 deletions
diff --git a/deps/g3dlite/source/FileSystem.cpp b/deps/g3dlite/source/FileSystem.cpp
new file mode 100644
index 0000000000..f082937a90
--- /dev/null
+++ b/deps/g3dlite/source/FileSystem.cpp
@@ -0,0 +1,874 @@
+/**
+ @file FileSystem.cpp
+
+ @author Morgan McGuire, http://graphics.cs.williams.edu
+
+ @author 2002-06-06
+ @edited 2010-04-10
+ */
+#include "G3D/FileSystem.h"
+#include "G3D/System.h"
+#include "G3D/stringutils.h"
+#include "G3D/fileutils.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#if _HAVE_ZIP /* G3DFIX: Use ZIP-library only if defined */
+ #include "zip.h"
+#endif
+#include "G3D/g3dfnmatch.h"
+#include "G3D/BinaryInput.h"
+#include "G3D/BinaryOutput.h"
+
+#ifdef G3D_WIN32
+ // Needed for _getcwd
+# include <direct.h>
+
+ // Needed for _findfirst
+# include <io.h>
+# ifdef __MINGW32__
+# define stat64 stat
+# else
+# define stat64 _stat64
+# endif
+#else
+# include <dirent.h>
+# include <fnmatch.h>
+# include <unistd.h>
+# define _getcwd getcwd
+# define _stat stat
+#endif
+
+#ifdef __CYGWIN__
+#define stat64 stat
+#endif
+
+namespace G3D {
+
+static FileSystem* common = NULL;
+
+FileSystem& FileSystem::instance() {
+ init();
+ return *common;
+}
+
+
+void FileSystem::init() {
+ if (common == NULL) {
+ common = new FileSystem();
+ }
+}
+
+
+void FileSystem::cleanup() {
+ if (common != NULL) {
+ delete common;
+ common = NULL;
+ }
+}
+
+FileSystem::FileSystem() : m_cacheLifetime(10) {}
+
+/////////////////////////////////////////////////////////////
+
+bool FileSystem::Dir::contains(const std::string& f) const {
+
+ for (int i = 0; i < nodeArray.size(); ++i) {
+# ifdef G3D_WIN32
+ if (stricmp(f.c_str(), nodeArray[i].name.c_str()) == 0) {
+ return true;
+ }
+# else
+ if (f == nodeArray[i].name) {
+ return true;
+ }
+# endif
+ }
+ return false;
+}
+
+void FileSystem::Dir::computeZipListing(const std::string& zipfile, const std::string& pathInsideZipfile) {
+#if _HAVE_ZIP /* G3DFIX: Use ZIP-library only if defined */
+ struct zip* z = zip_open( FilePath::removeTrailingSlash(zipfile).c_str(), ZIP_CHECKCONS, NULL );
+ debugAssert(z);
+
+ int count = zip_get_num_files( z );
+ Set<std::string> alreadyAdded;
+ for (int i = 0; i < count; ++i) {
+ struct zip_stat info;
+ zip_stat_init( &info ); // TODO: Docs unclear if zip_stat_init is required.
+ zip_stat_index( z, i, ZIP_FL_NOCASE, &info );
+
+ // Fully-qualified name of a file inside zipfile
+ std::string name = info.name;
+
+ if (beginsWith(name, pathInsideZipfile)) {
+ // We found something inside the directory we were looking for,
+ // so the directory itself must exist
+ exists = true;
+
+ // For building the cached directory listing, extract only elements that do not contain
+ // additional subdirectories.
+
+ int start = pathInsideZipfile.size();
+ if ((int(name.length()) > start) && isSlash(name[start])) {
+ ++start;
+ }
+ int end = findSlash(name, start);
+ if (end == -1) {
+ // There are no more slashes; add this name
+ name = name.substr(start);
+ if (alreadyAdded.insert(name)) {
+ Entry& e = nodeArray.next();
+ e.name = name;
+ e.type = FILE_TYPE;
+ }
+ } else {
+ // There are more slashes, indicating that this is a directory
+ name = name.substr(start, end);
+ if (alreadyAdded.insert(name)) {
+ Entry& e = nodeArray.next();
+ e.name = name;
+ e.type = DIR_TYPE;
+ }
+ }
+ }
+ }
+
+ zip_close(z);
+ z = NULL;
+#endif
+}
+
+
+FileSystem::Dir& FileSystem::getContents(const std::string& path, bool forceUpdate) {
+ const std::string& key =
+# if defined(G3D_WIN32)
+ FilePath::canonicalize(FilePath::removeTrailingSlash(toLower(FilePath::canonicalize(resolve(path)))));
+# else
+ FilePath::canonicalize(FilePath::removeTrailingSlash(FilePath::canonicalize(resolve(path))));
+# endif
+
+ RealTime now = System::time();
+ Dir& dir = m_cache.getCreate(key);
+
+ if ((now > dir.lastChecked + cacheLifetime()) || forceUpdate) {
+ dir = Dir();
+
+ // Out of date: update
+ dir.lastChecked = now;
+
+ struct _stat st;
+ const bool exists = _stat(key.c_str(), &st) != -1;
+ const bool isDirectory = (st.st_mode & S_IFDIR) != 0;
+
+ // Does this path exist on the real filesystem?
+ if (exists && isDirectory) {
+
+ // Is this path actually a directory?
+ if (isDirectory) {
+ dir.exists = true;
+ // Update contents
+# ifdef G3D_WIN32
+ const std::string& filespec = FilePath::concat(key, "*");
+ struct _finddata_t fileinfo;
+ intptr_t handle = _findfirst(filespec.c_str(), &fileinfo);
+ debugAssert(handle != -1);
+ int result = 0;
+ do {
+ if ((strcmp(fileinfo.name, ".") != 0) && (strcmp(fileinfo.name, "..") != 0)) {
+ Entry& e = dir.nodeArray.next();
+ e.name = fileinfo.name;
+ if ((fileinfo.attrib & _A_SUBDIR) != 0) {
+ e.type = DIR_TYPE;
+ } else {
+ e.type = FILE_TYPE;
+ }
+ }
+
+ result = _findnext(handle, &fileinfo);
+ } while (result == 0);
+ _findclose(handle);
+
+# else
+ DIR* listing = opendir(key.c_str());
+ debugAssertM(listing, "opendir failed on '" + key + "'");
+ struct dirent* entry = readdir(listing);
+ while (entry != NULL) {
+ if ((strcmp(entry->d_name, "..") != 0) && (strcmp(entry->d_name, ".") != 0)) {
+ Entry& e = dir.nodeArray.next();
+ e.name = entry->d_name;
+
+# ifdef _DIRENT_HAVE_D_TYPE
+ // Not all POSIX systems support this field
+ // http://www.delorie.com/gnu/docs/glibc/libc_270.html
+ switch (entry->d_type) {
+ case DT_DIR:
+ e.type = DIR_TYPE;
+ break;
+
+ case DT_REG:
+ e.type = FILE_TYPE;
+ break;
+
+ case DT_UNKNOWN:
+ default:
+ e.type = UNKNOWN;
+ break;
+ }
+# endif
+ }
+ entry = readdir(listing);
+ }
+ closedir(listing);
+ listing = NULL;
+ entry = NULL;
+# endif
+ }
+
+ } else {
+ std::string zip;
+
+ if (exists && isZipfile(path)) {
+ // This is a zipfile; get its root
+ dir.isZipfile = true;
+ dir.computeZipListing(path, "");
+
+ } else if (inZipfile(path, zip)) {
+
+ // There is a zipfile somewhere in the path. Does
+ // the rest of the path exist inside the zipfile?
+ dir.inZipfile = true;
+
+ dir.computeZipListing(zip, path.substr(zip.length() + 1));
+ }
+ }
+ }
+
+ return dir;
+}
+
+
+bool FileSystem::_inZipfile(const std::string& path, std::string& z) {
+ // Reject trivial cases before parsing
+ if (path.find('.') == std::string::npos) {
+ // There is no zipfile possible, since G3D requires
+ // an extension on zipfiles.
+ return false;
+ }
+
+ // Look at all sub-paths containing periods.
+ // For each, ask if it is a zipfile.
+ int current = 0;
+ current = path.find('.', current);
+
+ while (current != -1) {
+ // xxxxx/foo.zip/yyyyy
+ current = path.find('.', current);
+
+ // Look forward for the next slash
+ int s = findSlash(path, current);
+
+ if (s == -1) {
+ // No more slashes
+ return false;
+ }
+
+ z = path.substr(0, s);
+ if (_isZipfile(z)) {
+ return true;
+ }
+
+ current = s + 1;
+ }
+
+ z = "";
+ return false;
+}
+
+
+bool FileSystem::_isZipfile(const std::string& filename) {
+ if (FilePath::ext(filename).empty()) {
+ return false;
+ }
+
+ FILE* f = fopen(FilePath::removeTrailingSlash(filename).c_str(), "r");
+ if (f == NULL) {
+ return false;
+ }
+ uint8 header[4];
+ fread(header, 4, 1, f);
+
+ const uint8 zipHeader[4] = {0x50, 0x4b, 0x03, 0x04};
+ for (int i = 0; i < 4; ++i) {
+ if (header[i] != zipHeader[i]) {
+ fclose(f);
+ return false;
+ }
+ }
+
+ fclose(f);
+ return true;
+}
+
+
+FILE* FileSystem::_fopen(const char* filename, const char* mode) {
+ for (const char* m = mode; *m != '\0'; ++m) {
+ if (*m == 'w') {
+ // Purge the cache entry for the parent of this directory
+ _clearCache(FilePath::parent(_resolve(filename)));
+ break;
+ }
+ }
+ return ::fopen(filename, mode);
+}
+
+
+void FileSystem::_clearCache(const std::string& path) {
+ if ((path == "") || FilePath::isRoot(path)) {
+ m_cache.clear();
+ } else {
+ Array<std::string> keys;
+ m_cache.getKeys(keys);
+
+ const std::string& prefix =
+# ifdef G3D_WIN32
+ toLower(FilePath::canonicalize(FilePath::removeTrailingSlash(_resolve(path))));
+# else
+ FilePath::canonicalize(FilePath::removeTrailingSlash(_resolve(path)));
+# endif
+ const std::string& prefixSlash = prefix + "/";
+
+ for (int k = 0; k < keys.size(); ++k) {
+ const std::string& key = keys[k];
+ if ((key == prefix) || beginsWith(key, prefixSlash)) {
+ m_cache.remove(keys[k]);
+ }
+ }
+ }
+}
+
+
+void FileSystem::_setCacheLifetime(float t) {
+ m_cacheLifetime = t;
+}
+
+
+void FileSystem::_createDirectory(const std::string& dir) {
+
+ if (dir == "") {
+ return;
+ }
+
+ std::string d = _resolve(dir);
+
+ // Add a trailing / if there isn't one.
+ switch (d[d.size() - 1]) {
+ case '/':
+ case '\\':
+ break;
+
+ default:
+ d += "/";
+ }
+
+ // If it already exists, do nothing
+ if (_exists(FilePath::removeTrailingSlash(d))) {
+ return;
+ }
+
+ // Parse the name apart
+ std::string root, base, ext;
+ Array<std::string> path;
+
+ std::string lead;
+ FilePath::parse(d, root, path, base, ext);
+ debugAssert(base == "");
+ debugAssert(ext == "");
+
+ // Begin with an extra period so "c:\" becomes "c:\.\" after
+ // appending a path and "c:" becomes "c:.\", not root: "c:\"
+ std::string p = root + ".";
+
+ // Create any intermediate that doesn't exist
+ for (int i = 0; i < path.size(); ++i) {
+ p += "/" + path[i];
+ if (! _exists(p)) {
+ // Windows only requires one argument to mkdir,
+ // where as unix also requires the permissions.
+# ifndef G3D_WIN32
+ mkdir(p.c_str(), 0777);
+# else
+ _mkdir(p.c_str());
+# endif
+ }
+ }
+
+ _clearCache(FilePath::parent(FilePath::removeTrailingSlash(d)));
+}
+
+
+void FileSystem::_copyFile(const std::string& source, const std::string& dest) {
+# ifdef G3D_WIN32
+ // TODO: handle case where srcPath is in a zipfile
+ CopyFileA(source.c_str(), dest.c_str(), FALSE);
+ _clearCache(FilePath::parent(_resolve(dest)));
+# else
+ // Read it all in, then dump it out
+ BinaryInput in(source, G3D_LITTLE_ENDIAN);
+ BinaryOutput out(dest, G3D_LITTLE_ENDIAN);
+ out.writeBytes(in.getCArray(), in.size());
+ out.commit(false);
+# endif
+}
+
+
+bool FileSystem::_exists(const std::string& f, bool trustCache) {
+
+ if (FilePath::isRoot(f)) {
+# ifdef G3D_WIN32
+ const std::string& winname = toLower(f.substr(0, 1)) + ":\\";
+ return _drives().contains(winname);
+# else
+ return true;
+# endif
+ }
+
+ std::string path = FilePath::removeTrailingSlash(f);
+ std::string parentPath = FilePath::parent(path);
+
+ const Dir& entry = getContents(parentPath, ! trustCache);
+
+ if (FilePath::containsWildcards(f)) {
+ if (! entry.exists) {
+ // The directory didn't exist, so neither do its contents
+ return false;
+ }
+
+ const std::string& pattern = FilePath::baseExt(path);
+
+# ifdef G3D_WIN32
+ static const int flags = FNM_CASEFOLD;
+# else
+ static const int flags = 0;
+# endif
+
+ // See if any element of entry matches the wild card
+ for (int i = 0; i < entry.nodeArray.size(); ++i) {
+ if (FilePath::matches(entry.nodeArray[i].name, pattern, flags)) {
+ return true;
+ }
+ }
+
+ // Could not find a match
+ return false;
+
+ } else {
+ return entry.exists && entry.contains(FilePath::baseExt(path));
+ }
+}
+
+
+bool FileSystem::_isDirectory(const std::string& filename) {
+ // TODO: work with zipfiles and cache
+ struct _stat st;
+ const bool exists = _stat(FilePath::removeTrailingSlash(filename).c_str(), &st) != -1;
+ return exists && ((st.st_mode & S_IFDIR) != 0);
+}
+
+
+std::string FileSystem::_resolve(const std::string& filename, const std::string& cwd) {
+ if (filename.size() >= 1) {
+ if (isSlash(filename[0])) {
+ // Already resolved
+ return filename;
+ } else {
+
+ #ifdef G3D_WIN32
+ if ((filename.size() >= 2) && (filename[1] == ':')) {
+ // There is a drive spec on the front.
+ if ((filename.size() >= 3) && isSlash(filename[2])) {
+ // Already fully qualified
+ return filename;
+ } else {
+ // The drive spec is relative to the
+ // working directory on that drive.
+ debugAssertM(false, "Files of the form d:path are"
+ " not supported (use a fully qualified"
+ " name).");
+ return filename;
+ }
+ }
+ #endif
+ }
+ }
+
+ // Prepend the working directory.
+ return FilePath::concat(cwd, filename);
+}
+
+
+std::string FileSystem::_currentDirectory() {
+ static const int N = 2048;
+ char buffer[N];
+
+ _getcwd(buffer, N);
+ return std::string(buffer);
+}
+
+
+bool FileSystem::_isNewer(const std::string& src, const std::string& dst) {
+ // TODO: work with cache and zipfiles
+ struct _stat sts;
+ bool sexists = _stat(src.c_str(), &sts) != -1;
+
+ struct _stat dts;
+ bool dexists = _stat(dst.c_str(), &dts) != -1;
+
+ return sexists && ((! dexists) || (sts.st_mtime > dts.st_mtime));
+}
+
+
+int64 FileSystem::_size(const std::string& filename) {
+ struct stat64 st;
+ int result = stat64(filename.c_str(), &st);
+
+ if (result == -1) {
+#if _HAVE_ZIP /* G3DFIX: Use ZIP-library only if defined */
+ std::string zip, contents;
+ if (zipfileExists(filename, zip, contents)) {
+ int64 requiredMem;
+
+ struct zip *z = zip_open( zip.c_str(), ZIP_CHECKCONS, NULL );
+ debugAssertM(z != NULL, zip + ": zip open failed.");
+ {
+ struct zip_stat info;
+ zip_stat_init( &info ); // Docs unclear if zip_stat_init is required.
+ int success = zip_stat( z, contents.c_str(), ZIP_FL_NOCASE, &info );
+ debugAssertM(success == 0, zip + ": " + contents + ": zip stat failed.");
+ requiredMem = info.size;
+ }
+ zip_close(z);
+ return requiredMem;
+ } else {
+#endif
+ return -1;
+#if _HAVE_ZIP /* G3DFIX: Use ZIP-library only if defined */
+ }
+#endif
+ }
+
+ return st.st_size;
+}
+
+
+void FileSystem::listHelper(const std::string& shortSpec, const std::string& parentPath, Array<std::string>& result, const ListSettings& settings) {
+ Dir& dir = getContents(parentPath, false);
+
+ if (! dir.exists) {
+ return;
+ }
+
+ for (int i = 0; i < dir.nodeArray.size(); ++i) {
+ Entry& entry = dir.nodeArray[i];
+ // See if it matches the spec
+ if (FilePath::matches(entry.name, shortSpec, settings.caseSensitive)) {
+
+ if ((entry.type == UNKNOWN) && ! (settings.files && settings.directories)) {
+ // Update the type
+ entry.type = isDirectory(FilePath::concat(parentPath, entry.name)) ? DIR_TYPE : FILE_TYPE;
+ }
+
+ if ((settings.files && settings.directories) ||
+ (settings.files && (entry.type == FILE_TYPE)) ||
+ (settings.directories && (entry.type == DIR_TYPE))) {
+
+ if (settings.includeParentPath) {
+ result.append(FilePath::concat(parentPath, entry.name));
+ } else {
+ result.append(entry.name);
+ }
+ }
+ } // match
+
+ if (settings.recursive && (entry.type == DIR_TYPE)) {
+ listHelper(shortSpec, FilePath::concat(parentPath, entry.name), result, settings);
+ }
+ } // for
+}
+
+
+void FileSystem::_list(const std::string& spec, Array<std::string>& result, const ListSettings& settings) {
+ const std::string& shortSpec = FilePath::baseExt(spec);
+ const std::string& parentPath = FilePath::parent(spec);
+
+ listHelper(shortSpec, parentPath, result, settings);
+}
+
+
+
+#ifdef G3D_WIN32
+const Array<std::string>& FileSystem::_drives() {
+ if (m_winDrive.length() == 0) {
+ // See http://msdn.microsoft.com/en-us/library/aa364975(VS.85).aspx
+ static const size_t bufSize = 5000;
+ char bufData[bufSize];
+ GetLogicalDriveStringsA(bufSize, bufData);
+
+ // Drive list is a series of NULL-terminated strings, itself terminated with a NULL.
+ for (int i = 0; bufData[i] != '\0'; ++i) {
+ const char* thisString = bufData + i;
+ m_winDrive.append(toLower(thisString));
+ i += strlen(thisString) + 1;
+ }
+ }
+
+ return m_winDrive;
+}
+#endif
+
+/////////////////////////////////////////////////////////////////////
+
+bool FilePath::isRoot(const std::string& f) {
+# ifdef G3D_WIN32
+ if (f.length() < 2) {
+ return false;
+ }
+
+ if (f[1] == ':') {
+ if (f.length() == 2) {
+ // e.g., "x:"
+ return true;
+ } else if ((f.length() == 3) && isSlash(f[2])) {
+ // e.g., "x:\"
+ return true;
+ }
+ }
+
+ if (isSlash(f[0]) && isSlash(f[1])) {
+ // e.g., "\\foo\"
+ return true;
+ }
+# else
+ if (f == "/") {
+ return true;
+ }
+# endif
+
+ return false;
+}
+
+
+std::string FilePath::removeTrailingSlash(const std::string& f) {
+ bool removeSlash = ((endsWith(f, "/") || endsWith(f, "\\"))) && ! isRoot(f);
+
+ return removeSlash ? f.substr(0, f.length() - 1) : f;
+}
+
+
+std::string FilePath::concat(const std::string& dirname, const std::string& file) {
+ // Ensure that the directory ends in a slash
+ if (! dirname.empty() &&
+ ! isSlash(dirname[dirname.size() - 1]) &&
+ (dirname[dirname.size() - 1] != ':')) {
+ return dirname + '/' + file;
+ } else {
+ return dirname + file;
+ }
+}
+
+
+std::string FilePath::ext(const std::string& filename) {
+ int i = filename.rfind(".");
+ if (i >= 0) {
+ return filename.substr(i + 1, filename.length() - i);
+ } else {
+ return "";
+ }
+}
+
+
+std::string FilePath::baseExt(const std::string& filename) {
+ int i = findLastSlash(filename);
+
+# ifdef G3D_WIN32
+ int j = filename.rfind(":");
+ if ((i == -1) && (j >= 0)) {
+ i = j;
+ }
+# endif
+
+ if (i == -1) {
+ return filename;
+ } else {
+ return filename.substr(i + 1, filename.length() - i);
+ }
+}
+
+
+std::string FilePath::base(const std::string& path) {
+ std::string filename = baseExt(path);
+ int i = filename.rfind(".");
+ if (i == -1) {
+ // No extension
+ return filename;
+ } else {
+ return filename.substr(0, i);
+ }
+}
+
+
+std::string FilePath::parent(const std::string& path) {
+ int i = findLastSlash(removeTrailingSlash(path));
+
+# ifdef G3D_WIN32
+ int j = path.rfind(":");
+ if ((i == -1) && (j >= 0)) {
+ i = j;
+ }
+# endif
+
+ if (i == -1) {
+ return "";
+ } else {
+ return path.substr(0, i + 1);
+ }
+}
+
+
+bool FilePath::containsWildcards(const std::string& filename) {
+ return (filename.find('*') != std::string::npos) || (filename.find('?') != std::string::npos);
+}
+
+
+bool FilePath::matches(const std::string& path, const std::string& pattern, bool caseSensitive) {
+ int flags = FNM_PERIOD | FNM_NOESCAPE | FNM_PATHNAME;
+ if (! caseSensitive) {
+ flags |= FNM_CASEFOLD;
+ }
+ return g3dfnmatch(pattern.c_str(), path.c_str(), flags) == 0;
+}
+
+
+static int fixslash(int c) {
+ return (c == '\\') ? '/' : c;
+}
+
+
+std::string FilePath::canonicalize(std::string x) {
+ std::transform(x.begin(), x.end(), x.begin(), fixslash);
+ return x;
+}
+
+
+void FilePath::parse
+(const std::string& filename,
+ std::string& root,
+ Array<std::string>& path,
+ std::string& base,
+ std::string& ext) {
+
+ std::string f = filename;
+
+ root = "";
+ path.clear();
+ base = "";
+ ext = "";
+
+ if (f == "") {
+ // Empty filename
+ return;
+ }
+
+ // See if there is a root/drive spec.
+ if ((f.size() >= 2) && (f[1] == ':')) {
+
+ if ((f.size() > 2) && isSlash(f[2])) {
+
+ // e.g. c:\foo
+ root = f.substr(0, 3);
+ f = f.substr(3, f.size() - 3);
+
+ } else {
+
+ // e.g. c:foo
+ root = f.substr(2);
+ f = f.substr(2, f.size() - 2);
+
+ }
+
+ } else if ((f.size() >= 2) & isSlash(f[0]) && isSlash(f[1])) {
+
+ // e.g. //foo
+ root = f.substr(0, 2);
+ f = f.substr(2, f.size() - 2);
+
+ } else if (isSlash(f[0])) {
+
+ root = f.substr(0, 1);
+ f = f.substr(1, f.size() - 1);
+
+ }
+
+ // Pull the extension off
+ {
+ // Find the period
+ size_t i = f.rfind('.');
+
+ if (i != std::string::npos) {
+ // Make sure it is after the last slash!
+ size_t j = iMax(f.rfind('/'), f.rfind('\\'));
+ if ((j == std::string::npos) || (i > j)) {
+ ext = f.substr(i + 1, f.size() - i - 1);
+ f = f.substr(0, i);
+ }
+ }
+ }
+
+ // Pull the basename off
+ {
+ // Find the last slash
+ size_t i = iMax(f.rfind('/'), f.rfind('\\'));
+
+ if (i == std::string::npos) {
+
+ // There is no slash; the basename is the whole thing
+ base = f;
+ f = "";
+
+ } else if ((i != std::string::npos) && (i < f.size() - 1)) {
+
+ base = f.substr(i + 1, f.size() - i - 1);
+ f = f.substr(0, i);
+
+ }
+ }
+
+ // Parse what remains into path.
+ size_t prev, cur = 0;
+
+ while (cur < f.size()) {
+ prev = cur;
+
+ // Allow either slash
+ size_t i = f.find('/', prev + 1);
+ size_t j = f.find('\\', prev + 1);
+ if (i == std::string::npos) {
+ i = f.size();
+ }
+
+ if (j == std::string::npos) {
+ j = f.size();
+ }
+
+ cur = iMin(i, j);
+
+ if (cur == std::string::npos) {
+ cur = f.size();
+ }
+
+ path.append(f.substr(prev, cur - prev));
+ ++cur;
+ }
+}
+
+}