mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-21 11:56:23 +02:00
It used to return the path unchanged when the path didn't end in oldExtension, so a caller that guessed wrong silently went on using the original file - and "the screenshot next to this savestate" quietly becomes "this savestate". Every caller had to know to check the extension first, and most didn't. Now it's [[nodiscard]] bool with an out-param, in the style of ComputePathTo next door, so the mismatch has to be handled. Changing the signature rather than the behaviour means no call site can keep the old assumption by accident. All four callers wanted "skip it" or "fall back", which they now say out loud. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
168 lines
5.2 KiB
C++
168 lines
5.2 KiB
C++
#pragma once
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#ifdef HAVE_LIBRETRO_VFS
|
|
#include "libretro/LibretroFileStreamTransforms.h"
|
|
#endif
|
|
|
|
#if defined(__APPLE__)
|
|
|
|
#if TARGET_OS_IPHONE
|
|
#define HOST_IS_CASE_SENSITIVE 1
|
|
#elif TARGET_IPHONE_SIMULATOR
|
|
#define HOST_IS_CASE_SENSITIVE 0
|
|
#else
|
|
// Mac OSX case sensitivity defaults off, but is user configurable (when
|
|
// creating a filesytem), so assume the worst:
|
|
#define HOST_IS_CASE_SENSITIVE 1
|
|
#endif
|
|
|
|
#elif defined(_WIN32)
|
|
#define HOST_IS_CASE_SENSITIVE 0
|
|
|
|
#else // Android, Linux, BSD (and the rest?)
|
|
#define HOST_IS_CASE_SENSITIVE 1
|
|
|
|
#endif
|
|
|
|
enum class PathType {
|
|
UNDEFINED = 0,
|
|
NATIVE = 1, // Can be relative.
|
|
CONTENT_URI = 2, // Android only. Can only be absolute!
|
|
HTTP = 3, // http://, https://
|
|
};
|
|
|
|
// Windows paths are always stored with '/' slashes in a Path.
|
|
// On .ToWString(), they are flipped back to '\'.
|
|
|
|
class Path {
|
|
private:
|
|
void Init(std::string_view str);
|
|
|
|
public:
|
|
Path() : type_(PathType::UNDEFINED) {}
|
|
explicit Path(std::string_view str);
|
|
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
explicit Path(const std::wstring &str);
|
|
#endif
|
|
|
|
PathType Type() const {
|
|
return type_;
|
|
}
|
|
bool IsLocalType() const {
|
|
return type_ == PathType::NATIVE || type_ == PathType::CONTENT_URI;
|
|
}
|
|
|
|
bool Valid() const { return !path_.empty(); }
|
|
bool IsRoot() const { return path_ == "/"; } // Special value - only path that can end in a slash.
|
|
|
|
// Some std::string emulation for simplicity.
|
|
bool empty() const { return !Valid(); }
|
|
void clear() {
|
|
type_ = PathType::UNDEFINED;
|
|
path_.clear();
|
|
}
|
|
size_t size() const {
|
|
return path_.size();
|
|
}
|
|
|
|
// WARNING: Potentially unsafe usage, if it's not NATIVE.
|
|
const char *c_str() const {
|
|
return path_.c_str();
|
|
}
|
|
|
|
bool IsAbsolute() const;
|
|
|
|
// Returns a path extended with a subdirectory.
|
|
Path operator /(std::string_view subdir) const;
|
|
|
|
// Navigates down into a subdir.
|
|
void operator /=(std::string_view subdir);
|
|
|
|
// File extension manipulation.
|
|
Path WithExtraExtension(std::string_view ext) const;
|
|
// Swaps one known extension for another, e.g. ".ppst" -> ".jpg". Returns false and leaves *out
|
|
// alone if the path doesn't actually end in oldExtension. That case has to be handled: this
|
|
// used to return the path unchanged, so a caller that guessed the extension wrong quietly
|
|
// carried on with the original file - and "delete the screenshot next to this savestate" then
|
|
// means "delete the savestate".
|
|
[[nodiscard]] bool WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension, Path *out) const;
|
|
// Replaces whatever extension is there, so there's nothing to fail on.
|
|
Path WithReplacedExtension(const std::string &newExtension) const;
|
|
|
|
std::string GetFilename() const; // Really, GetLastComponent. Could be a file or directory. Includes the extension.
|
|
std::string GetFileExtension() const; // Always lowercase return. Includes the dot.
|
|
// Removes the last component.
|
|
std::string GetDirectory() const;
|
|
|
|
const std::string &ToString() const;
|
|
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
std::wstring ToWString() const;
|
|
std::string ToCString() const; // Flips the slashes back to Windows standard, but string still UTF-8.
|
|
#else
|
|
std::string ToCString() const {
|
|
return ToString();
|
|
}
|
|
#endif
|
|
|
|
// Pass in a relative root to turn the path into a relative path - if it is one!
|
|
std::string ToVisualString(const char *relativeRoot = nullptr) const;
|
|
|
|
bool CanNavigateUp() const;
|
|
Path NavigateUp() const;
|
|
|
|
// Navigates as far up as possible from this path. If not possible to navigate upwards, returns the same path.
|
|
// Not actually always the root of the volume, especially on systems like Mac and Linux where things are often mounted.
|
|
// For Android directory trees, navigates to the root of the tree.
|
|
Path GetRootVolume() const;
|
|
|
|
bool ComputePathTo(const Path &other, std::string &path) const;
|
|
|
|
bool operator ==(const Path &other) const {
|
|
return path_ == other.path_ && type_ == other.type_;
|
|
}
|
|
bool operator !=(const Path &other) const {
|
|
return path_ != other.path_ || type_ != other.type_;
|
|
}
|
|
|
|
bool FilePathContainsNoCase(std::string_view needle) const;
|
|
|
|
// WARNING: This can return wrong results if two ContentURI have different root paths.
|
|
bool StartsWith(const Path &other) const;
|
|
|
|
// This handles ContentURI with different paths "correctly". Very specific use case.
|
|
bool StartsWithGlobalAndNotEqual(const Path &other) const;
|
|
|
|
bool operator <(const Path &other) const {
|
|
return path_ < other.path_;
|
|
}
|
|
bool operator >(const Path &other) const {
|
|
return path_ > other.path_;
|
|
}
|
|
|
|
private:
|
|
// The internal representation is currently always the plain string.
|
|
// For CPU efficiency we could keep an AndroidStorageContentURI too,
|
|
// but I don't think the encode/decode cost is significant. We simply create
|
|
// those for processing instead.
|
|
std::string path_;
|
|
|
|
PathType type_;
|
|
};
|
|
|
|
// Utility function for fixing the case of paths. Only necessary on Unix-like systems.
|
|
|
|
enum FixPathCaseBehavior {
|
|
FPC_FILE_MUST_EXIST, // all path components must exist (rmdir, move from)
|
|
FPC_PATH_MUST_EXIST, // all except the last one must exist - still tries to fix last one (fopen, move to)
|
|
FPC_PARTIAL_ALLOWED, // don't care how many exist (mkdir recursive)
|
|
};
|
|
|
|
bool FixPathCase(const Path &basePath, std::string &path, FixPathCaseBehavior behavior);
|