flycast/core/oslib/storage.cpp

367 lines
9.8 KiB
C++

/*
Copyright 2023 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "storage.h"
#include "directory.h"
#include "stdclass.h"
// For macOS
std::string os_PrecomposedString(std::string string);
namespace hostfs
{
CustomStorage& customStorage();
#if !defined(__ANDROID__) || defined(LIBRETRO)
CustomStorage& customStorage()
{
class NullStorage : public CustomStorage
{
public:
bool isKnownPath(const std::string& path) override { return false; }
std::vector<FileInfo> listContent(const std::string& path) override { return std::vector<FileInfo>(); }
FILE *openFile(const std::string& path, const std::string& mode) override { die("Not implemented"); }
std::string getParentPath(const std::string& path) override { die("Not implemented"); }
std::string getSubPath(const std::string& reference, const std::string& relative) override { die("Not implemented"); }
FileInfo getFileInfo(const std::string& path) override { die("Not implemented"); }
void addStorage(bool isDirectory, bool writeAccess, void (*callback)(bool cancelled, std::string selectedPath)) override {
die("Not implemented");
}
};
static NullStorage storage;
return storage;
}
#endif
#ifdef _WIN32
static const std::string separators = "/\\";
static const std::string native_separator = "\\";
#else
static const std::string separators = "/";
static const std::string native_separator = "/";
#endif
class StdStorage : public Storage
{
public:
bool isKnownPath(const std::string& path) override {
// assume it's known since standard storage should be asked last
return true;
}
std::vector<FileInfo> listContent(const std::string& path) override
{
if (path.empty())
return listRoots();
DIR *dir = flycast::opendir(path.c_str());
if (dir == nullptr) {
WARN_LOG(COMMON, "Cannot read directory '%s' errno 0x%x", path.c_str(), errno);
throw StorageException("Can't read directory " + path);
}
std::vector<FileInfo> entries;
while (true)
{
dirent *direntry = flycast::readdir(dir);
if (direntry == nullptr)
break;
FileInfo entry;
entry.name = direntry->d_name;
#ifdef __APPLE__
entry.name = os_PrecomposedString(entry.name);
#endif
if (entry.name == "." || entry.name == "..")
continue;
if (path.find_last_of(separators) != path.size() - 1)
entry.path = path + native_separator + entry.name;
else
entry.path = path + entry.name;
// Silently skip unreadable entries
if (flycast::access(entry.path.c_str(), R_OK) != 0)
continue;
bool isDir = false;
#ifndef _WIN32
if (direntry->d_type == DT_DIR)
isDir = true;
else if (direntry->d_type == DT_UNKNOWN || direntry->d_type == DT_LNK)
{
struct stat st;
if (flycast::stat(entry.path.c_str(), &st) != 0)
{
WARN_LOG(COMMON, "Cannot stat file '%s' errno 0x%x", entry.path.c_str(), errno);
continue;
}
if (S_ISDIR(st.st_mode))
isDir = true;
}
#else // _WIN32
nowide::wstackstring wname;
if (wname.convert(entry.path.c_str()))
{
DWORD attr = GetFileAttributesW(wname.get());
if (attr != INVALID_FILE_ATTRIBUTES)
{
if (attr & FILE_ATTRIBUTE_HIDDEN)
continue;
if (attr & FILE_ATTRIBUTE_DIRECTORY)
isDir = true;
}
}
#endif
entry.isDirectory = isDir;
entries.emplace_back(entry);
}
flycast::closedir(dir);
return entries;
}
FILE *openFile(const std::string& path, const std::string& mode) override
{
return nowide::fopen(path.c_str(), mode.c_str());
}
std::string getParentPath(const std::string& path) override
{
auto lastSlash = path.find_last_of(separators);
#ifdef _WIN32
if (path.size() == 2 && std::isalpha(static_cast<u8>(path[0])) && path[1] == ':')
return "";
if (lastSlash == 2 && path.size() == 3 && std::isalpha(static_cast<u8>(path[0])) && path[1] == ':')
return "";
#else
if (lastSlash == 0)
return "/";
#endif
if (lastSlash == std::string::npos)
return "." + native_separator;
std::string parentPath = path.substr(0, lastSlash);
#ifdef _WIN32
if (parentPath.size() == 2 && std::isalpha(static_cast<u8>(parentPath[0])) && parentPath[1] == ':')
parentPath += '\\';
#endif
if (flycast::access(parentPath.c_str(), R_OK) != 0)
return "";
else
return parentPath;
}
std::string getSubPath(const std::string& reference, const std::string& subpath) override
{
// FIXME this is incorrect if reference isn't a directory
return reference + native_separator + subpath;
}
FileInfo getFileInfo(const std::string& path) override
{
FileInfo info;
info.path = path;
size_t slash = get_last_slash_pos(path);
if (slash != std::string::npos && slash < path.size() - 1)
info.name = path.substr(slash + 1);
else
info.name = path;
info.isWritable = flycast::access(path.c_str(), W_OK) == 0;
#ifndef _WIN32
struct stat st;
if (flycast::stat(path.c_str(), &st) != 0) {
INFO_LOG(COMMON, "Cannot stat file '%s' errno %d", path.c_str(), errno);
throw StorageException("Cannot stat " + path);
}
info.isDirectory = S_ISDIR(st.st_mode);
info.size = st.st_size;
#else // _WIN32
nowide::wstackstring wname;
if (wname.convert(path.c_str()))
{
WIN32_FILE_ATTRIBUTE_DATA fileAttribs;
if (GetFileAttributesExW(wname.get(), GetFileExInfoStandard, &fileAttribs))
{
info.isDirectory = (fileAttribs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
info.size = fileAttribs.nFileSizeLow + ((u64)fileAttribs.nFileSizeHigh << 32);
}
else
{
INFO_LOG(COMMON, "Cannot get attrbutes of '%s' error 0x%x", path.c_str(), GetLastError());
throw StorageException("Cannot get attributes of " + path);
}
}
else
{
throw StorageException("Invalid file name " + path);
}
#endif
return info;
}
private:
std::vector<FileInfo> listRoots()
{
#ifdef _WIN32
std::vector<FileInfo> driveList;
// List all the drives
u32 drives = GetLogicalDrives();
for (int i = 0; i < 32; i++)
if ((drives & (1 << i)) != 0)
{
std::string name = std::string(1, (char)('A' + i)) + ":";
driveList.emplace_back(name, name + "\\", true);
}
#ifdef TARGET_UWP
// Add the home directory to the list of drives as it's not accessible from the root
std::string home;
const char *home_drive = nowide::getenv("HOMEDRIVE");
if (home_drive != nullptr)
home = home_drive;
home += nowide::getenv("HOMEPATH");
driveList.emplace_back(home, home, true);
#endif
return driveList;
#elif defined(__ANDROID__)
std::vector<FileInfo> roots;
const char *home = nowide::getenv("FLYCAST_HOME");
while (home != nullptr)
{
const char *pcolon = strchr(home, ':');
if (pcolon != nullptr)
{
roots.emplace_back(std::string(home, pcolon - home), std::string(home, pcolon - home), true);
home = pcolon + 1;
}
else
{
roots.emplace_back(home, home, true);
home = nullptr;
}
}
return roots;
#else
return listContent("/");
#endif
}
};
static StdStorage stdStorage;
std::vector<FileInfo> AllStorage::listContent(const std::string& path)
{
if (path.empty())
{
std::vector<FileInfo> entries = stdStorage.listContent(path);
std::vector<FileInfo> customEntries = customStorage().listContent(path);
entries.insert(entries.end(), customEntries.begin(), customEntries.end());
return entries;
}
if (customStorage().isKnownPath(path))
return customStorage().listContent(path);
else
return stdStorage.listContent(path);
}
FILE *AllStorage::openFile(const std::string& path, const std::string& mode)
{
if (customStorage().isKnownPath(path))
return customStorage().openFile(path, mode);
else
return stdStorage.openFile(path, mode);
}
std::string AllStorage::getParentPath(const std::string& path)
{
if (customStorage().isKnownPath(path))
return customStorage().getParentPath(path);
else
return stdStorage.getParentPath(path);
}
std::string AllStorage::getSubPath(const std::string& reference, const std::string& subpath)
{
if (customStorage().isKnownPath(reference))
return customStorage().getSubPath(reference, subpath);
else
return stdStorage.getSubPath(reference, subpath);
}
FileInfo AllStorage::getFileInfo(const std::string& path)
{
if (customStorage().isKnownPath(path))
return customStorage().getFileInfo(path);
else
return stdStorage.getFileInfo(path);
}
std::string AllStorage::getDefaultDirectory()
{
std::string directory;
#if defined(__ANDROID__)
const char *home = nowide::getenv("FLYCAST_HOME");
if (home != NULL)
{
const char *pcolon = strchr(home, ':');
if (pcolon != NULL)
directory = std::string(home, pcolon - home);
else
directory = home;
}
#elif defined(__unix__) || defined(__APPLE__)
const char *home = nowide::getenv("HOME");
if (home != NULL)
directory = home;
#elif defined(_WIN32)
if (nowide::getenv("HOMEPATH") != NULL)
{
const char *home_drive = nowide::getenv("HOMEDRIVE");
if (home_drive != NULL)
directory = home_drive;
directory += nowide::getenv("HOMEPATH");
}
#elif defined(__SWITCH__)
directory = "/";
#endif
if (directory.empty())
{
directory = get_writable_config_path("");
if (directory.empty())
directory = ".";
}
return directory;
}
AllStorage& storage()
{
static AllStorage storage;
return storage;
}
void addStorage(bool isDirectory, bool writeAccess, void (*callback)(bool cancelled, std::string selectedPath))
{
customStorage().addStorage(isDirectory, writeAccess, callback);
}
}