CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Common/File/Path.cpp
Views: 1401
#include "ppsspp_config.h"1#include <algorithm>2#include <cctype>3#include <cstring>45#include "Common/File/Path.h"6#include "Common/File/AndroidContentURI.h"7#include "Common/File/FileUtil.h"8#include "Common/StringUtils.h"9#include "Common/Log.h"10#include "Common/Data/Encoding/Utf8.h"1112#include "android/jni/app-android.h"1314#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)15#include "UWP/UWPHelpers/StorageManager.h"16#endif1718#if HOST_IS_CASE_SENSITIVE19#include <dirent.h>20#include <unistd.h>21#include <sys/stat.h>22#endif2324Path::Path(std::string_view str) {25Init(str);26}2728#if PPSSPP_PLATFORM(WINDOWS)29Path::Path(const std::wstring &str) {30type_ = PathType::NATIVE;31Init(ConvertWStringToUTF8(str));32}33#endif3435void Path::Init(std::string_view str) {36if (str.empty()) {37type_ = PathType::UNDEFINED;38path_.clear();39} else if (startsWith(str, "http://") || startsWith(str, "https://")) {40type_ = PathType::HTTP;41path_ = str;42} else if (Android_IsContentUri(str)) {43// Content URIs on non scoped-storage (and possibly other cases?) can contain44// "raw:/" URIs inside. This happens when picking the Download folder using the folder browser45// on Android 9.46// Here's an example:47// content://com.android.providers.downloads.documents/tree/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fp/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fp48//49// Since this is a legacy use case, I think it's safe enough to just detect this50// and flip it to a NATIVE url and hope for the best.51AndroidContentURI uri(str);52if (startsWith(uri.FilePath(), "raw:/")) {53INFO_LOG(Log::System, "Raw path detected: %s", uri.FilePath().c_str());54path_ = uri.FilePath().substr(4);55type_ = PathType::NATIVE;56} else {57// A normal content URI path.58type_ = PathType::CONTENT_URI;59path_ = str;60}61} else {62type_ = PathType::NATIVE;63path_ = str;64}6566#if PPSSPP_PLATFORM(WINDOWS)67// Flip all the slashes around. We flip them back on ToWString().68for (size_t i = 0; i < path_.size(); i++) {69if (path_[i] == '\\') {70path_[i] = '/';71}72}73#endif7475// Don't pop_back if it's just "/".76if (type_ == PathType::NATIVE && path_.size() > 1 && path_.back() == '/') {77path_.pop_back();78}79}8081// We always use forward slashes internally, we convert to backslash only when82// converted to a wstring.83Path Path::operator /(std::string_view subdir) const {84if (type_ == PathType::CONTENT_URI) {85AndroidContentURI uri(path_);86return Path(uri.WithComponent(subdir).ToString());87}8889// Direct string manipulation.9091if (subdir.empty()) {92return Path(path_);93}94std::string fullPath = path_;95if (subdir.front() != '/' && (fullPath.empty() || fullPath.back() != '/')) {96fullPath += "/";97}98fullPath += subdir;99// Prevent adding extra slashes.100if (fullPath.back() == '/') {101fullPath.pop_back();102}103return Path(fullPath);104}105106void Path::operator /=(std::string_view subdir) {107*this = *this / subdir;108}109110Path Path::WithExtraExtension(std::string_view ext) const {111if (type_ == PathType::CONTENT_URI) {112AndroidContentURI uri(path_);113return Path(uri.WithExtraExtension(ext).ToString());114}115116_dbg_assert_(!ext.empty() && ext[0] == '.');117return Path(path_ + std::string(ext));118}119120Path Path::WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const {121if (type_ == PathType::CONTENT_URI) {122AndroidContentURI uri(path_);123return Path(uri.WithReplacedExtension(oldExtension, newExtension).ToString());124}125126_dbg_assert_(!oldExtension.empty() && oldExtension[0] == '.');127_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');128if (endsWithNoCase(path_, oldExtension)) {129std::string newPath = path_.substr(0, path_.size() - oldExtension.size());130return Path(newPath + newExtension);131} else {132return Path(*this);133}134}135136Path Path::WithReplacedExtension(const std::string &newExtension) const {137if (type_ == PathType::CONTENT_URI) {138AndroidContentURI uri(path_);139return Path(uri.WithReplacedExtension(newExtension).ToString());140}141142_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');143if (path_.empty()) {144return Path(*this);145}146std::string extension = GetFileExtension();147std::string newPath = path_.substr(0, path_.size() - extension.size()) + newExtension;148return Path(newPath);149}150151std::string Path::GetFilename() const {152if (type_ == PathType::CONTENT_URI) {153AndroidContentURI uri(path_);154return uri.GetLastPart();155}156size_t pos = path_.rfind('/');157if (pos != std::string::npos) {158return path_.substr(pos + 1);159}160return path_;161}162163std::string GetExtFromString(std::string_view str) {164size_t pos = str.rfind(".");165if (pos == std::string::npos) {166return "";167}168size_t slash_pos = str.rfind("/");169if (slash_pos != std::string::npos && slash_pos > pos) {170// Don't want to detect "df/file" from "/as.df/file"171return "";172}173std::string ext(str.substr(pos));174for (size_t i = 0; i < ext.size(); i++) {175ext[i] = tolower(ext[i]);176}177return ext;178}179180std::string Path::GetFileExtension() const {181if (type_ == PathType::CONTENT_URI) {182AndroidContentURI uri(path_);183return uri.GetFileExtension();184}185return GetExtFromString(path_);186}187188std::string Path::GetDirectory() const {189if (type_ == PathType::CONTENT_URI) {190// Unclear how meaningful this is.191AndroidContentURI uri(path_);192uri.NavigateUp();193return uri.ToString();194}195196size_t pos = path_.rfind('/');197if (type_ == PathType::HTTP) {198// Things are a bit different for HTTP, because we probably ended with /.199if (pos + 1 == path_.size()) {200pos = path_.rfind('/', pos - 1);201if (pos != path_.npos && pos > 8) {202return path_.substr(0, pos + 1);203}204}205}206207if (pos != std::string::npos) {208if (pos == 0) {209return "/"; // We're at the root.210}211return path_.substr(0, pos);212#if PPSSPP_PLATFORM(WINDOWS)213} else if (path_.size() == 2 && path_[1] == ':') {214// Windows fake-root.215return "/";216#endif217} else {218// There could be a ':', too. Unlike the slash, let's include that219// in the returned directory.220size_t c_pos = path_.rfind(':');221if (c_pos != std::string::npos) {222return path_.substr(0, c_pos + 1);223}224}225// No directory components, we're a relative path.226return path_;227}228229bool Path::FilePathContainsNoCase(std::string_view needle) const {230std::string haystack;231if (type_ == PathType::CONTENT_URI) {232haystack = AndroidContentURI(path_).FilePath();233} else {234haystack = path_;235}236auto pred = [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); };237auto found = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), pred);238return found != haystack.end();239}240241bool Path::StartsWith(const Path &other) const {242if (other.empty()) {243return true;244}245if (type_ != other.type_) {246// Bad247return false;248}249return startsWith(path_, other.path_);250}251252const std::string &Path::ToString() const {253return path_;254}255256#if PPSSPP_PLATFORM(WINDOWS)257std::wstring Path::ToWString() const {258std::wstring w = ConvertUTF8ToWString(path_);259for (size_t i = 0; i < w.size(); i++) {260if (w[i] == '/') {261w[i] = '\\';262}263}264return w;265}266std::string Path::ToCString() const {267std::string w = path_;268for (size_t i = 0; i < w.size(); i++) {269if (w[i] == '/') {270w[i] = '\\';271}272}273return w;274}275#endif276277std::string Path::ToVisualString(const char *relativeRoot) const {278if (type_ == PathType::CONTENT_URI) {279return AndroidContentURI(path_).ToVisualString();280#if PPSSPP_PLATFORM(WINDOWS)281} else if (type_ == PathType::NATIVE) {282#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)283return GetPreviewPath(path_);284#else285// It can be useful to show the path as relative to the memstick286if (relativeRoot) {287std::string root = ReplaceAll(relativeRoot, "/", "\\");288std::string path = ReplaceAll(path_, "/", "\\");289if (startsWithNoCase(path, root)) {290return path.substr(root.size());291} else {292return path;293}294} else {295return ReplaceAll(path_, "/", "\\");296}297#endif298#else299if (relativeRoot) {300std::string root = relativeRoot;301if (startsWithNoCase(path_, root)) {302return path_.substr(root.size());303} else {304return path_;305}306} else {307return path_;308}309#endif310} else {311return path_;312}313}314315bool Path::CanNavigateUp() const {316if (type_ == PathType::CONTENT_URI) {317return AndroidContentURI(path_).CanNavigateUp();318} else if (type_ == PathType::HTTP) {319size_t rootSlash = path_.find_first_of('/', strlen("https://"));320if (rootSlash == path_.npos || path_.size() == rootSlash + 1) {321// This means, "http://server" or "http://server/". Can't go up.322return false;323}324}325if (path_ == "/" || path_.empty()) {326return false;327}328return true;329}330331Path Path::NavigateUp() const {332if (type_ == PathType::CONTENT_URI) {333AndroidContentURI uri(path_);334uri.NavigateUp();335return Path(uri.ToString());336}337std::string dir = GetDirectory();338return Path(dir);339}340341Path Path::GetRootVolume() const {342if (!IsAbsolute()) {343// Can't do anything344return Path(path_);345}346347if (type_ == PathType::CONTENT_URI) {348AndroidContentURI uri(path_);349AndroidContentURI rootPath = uri.WithRootFilePath("");350return Path(rootPath.ToString());351}352353#if PPSSPP_PLATFORM(WINDOWS)354if (path_[1] == ':') {355// Windows path with drive letter356std::string path = path_.substr(0, 2);357return Path(path);358}359// Support UNC and device paths.360if (path_[0] == '/' && path_[1] == '/') {361size_t next = 2;362if ((path_[2] == '.' || path_[2] == '?') && path_[3] == '/') {363// Device path, or "\\.\UNC" path, skip the dot and consider the device the root.364next = 4;365}366367size_t len = path_.find_first_of('/', next);368return Path(path_.substr(0, len));369}370#endif371return Path("/");372}373374bool Path::IsAbsolute() const {375if (type_ == PathType::CONTENT_URI) {376// These don't exist in relative form.377return true;378}379380if (path_.empty())381return true;382else if (path_.front() == '/')383return true;384#if PPSSPP_PLATFORM(WINDOWS)385else if (path_.size() > 3 && path_[1] == ':')386return true; // Windows path with drive letter387#endif388else389return false;390}391392bool Path::ComputePathTo(const Path &other, std::string &path) const {393if (other == *this) {394path.clear();395return true;396}397398if (!other.StartsWith(*this)) {399// Can't do this. Should return an error.400return false;401}402403if (*this == other) {404// Equal, the path is empty.405path.clear();406return true;407}408409if (type_ == PathType::CONTENT_URI) {410AndroidContentURI a(path_);411AndroidContentURI b(other.path_);412if (a.RootPath() != b.RootPath()) {413// No common root, can't do anything414return false;415}416return a.ComputePathTo(b, path);417} else if (path_ == "/") {418path = other.path_.substr(1);419return true;420} else {421path = other.path_.substr(path_.size() + 1);422return true;423}424}425426#if HOST_IS_CASE_SENSITIVE427428static bool FixFilenameCase(const std::string &path, std::string &filename) {429// Are we lucky?430if (File::Exists(Path(path + filename)))431return true;432433size_t filenameSize = filename.size(); // size in bytes, not characters434for (size_t i = 0; i < filenameSize; i++)435{436filename[i] = tolower(filename[i]);437}438439//TODO: lookup filename in cache for "path"440struct dirent *result = NULL;441442DIR *dirp = opendir(path.c_str());443if (!dirp)444return false;445446bool retValue = false;447448while ((result = readdir(dirp)))449{450if (strlen(result->d_name) != filenameSize)451continue;452453size_t i;454for (i = 0; i < filenameSize; i++)455{456if (filename[i] != tolower(result->d_name[i]))457break;458}459460if (i < filenameSize)461continue;462463filename = result->d_name;464retValue = true;465}466467closedir(dirp);468469return retValue;470}471472bool FixPathCase(const Path &realBasePath, std::string &path, FixPathCaseBehavior behavior) {473if (realBasePath.Type() == PathType::CONTENT_URI) {474// Nothing to do. These are already case insensitive, I think.475return true;476}477478std::string basePath = realBasePath.ToString();479480size_t len = path.size();481482if (len == 0)483return true;484485if (path[len - 1] == '/')486{487len--;488489if (len == 0)490return true;491}492493std::string fullPath;494fullPath.reserve(basePath.size() + len + 1);495fullPath.append(basePath);496497size_t start = 0;498while (start < len)499{500size_t i = path.find('/', start);501if (i == std::string::npos)502i = len;503504if (i > start)505{506std::string component = path.substr(start, i - start);507508// Fix case and stop on nonexistant path component509if (FixFilenameCase(fullPath, component) == false) {510// Still counts as success if partial matches allowed or if this511// is the last component and only the ones before it are required512return (behavior == FPC_PARTIAL_ALLOWED || (behavior == FPC_PATH_MUST_EXIST && i >= len));513}514515path.replace(start, i - start, component);516517fullPath.append(1, '/');518fullPath.append(component);519}520521start = i + 1;522}523524return true;525}526527#endif528529530