Files
ppsspp/Common/File/AndroidContentURI.h
T
Henrik RydgårdandClaude Opus 5 4ae682283c Path: make WithReplacedExtension(old, new) report a mismatch instead of hiding it
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>
2026-09-12 11:54:11 -06:00

79 lines
2.3 KiB
C++

#pragma once
#include <string>
#include "Common/StringUtils.h"
#include "Common/Net/URL.h"
#include "Common/Log.h"
// Utility to deal with Android storage URIs of the forms:
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO
// This file compiles on all platforms, to reduce the need for ifdefs.
// I am not 100% sure it's OK to rely on the internal format of file content URIs.
// On the other hand, I'm sure tons of apps would break if these changed, so I think we can
// consider them pretty stable. Additionally, the official Document library just manipulates the URIs
// in similar ways...
class AndroidContentURI {
private:
std::string provider;
std::string root;
std::string file;
public:
AndroidContentURI() {}
explicit AndroidContentURI(std::string_view path) {
Parse(path);
}
bool Parse(std::string_view path);
AndroidContentURI WithRootFilePath(const std::string &filePath);
AndroidContentURI WithComponent(std::string_view filePath);
AndroidContentURI WithExtraExtension(std::string_view extension); // The ext string contains the dot.
// False, leaving *out alone, if the file doesn't end in oldExtension. See Path's version.
[[nodiscard]] bool WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension, AndroidContentURI *out) const;
AndroidContentURI WithReplacedExtension(const std::string &newExtension) const;
bool CanNavigateUp() const;
// Only goes downwards in hierarchies. No ".." will ever be generated.
bool ComputePathTo(const AndroidContentURI &other, std::string &path) const;
std::string GetFileExtension() const;
std::string GetLastPart() const;
bool NavigateUp();
bool TreeContains(const AndroidContentURI &fileURI) {
if (root.empty()) {
return false;
}
return startsWith(fileURI.file, root);
}
std::string ToString() const;
// Never store the output of this, only show it to the user.
std::string ToVisualString() const {
return file;
}
const std::string &FilePath() const {
return file;
}
const std::string &RootPath() const {
return root.empty() ? file : root;
}
const std::string &Provider() const {
return provider;
}
bool IsTreeURI() const {
return !root.empty();
}
};