diff --git a/CMakeLists.txt b/CMakeLists.txt index 88c93137a94..86b309cc2af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,8 @@ add_library(${PROJECT_NAME} OBJECT src/fileext_guesser.h src/filesystem.cpp src/filesystem.h + src/filesystem_drive.cpp + src/filesystem_drive.h src/filesystem_lzh.cpp src/filesystem_lzh.h src/filesystem_native.cpp diff --git a/Makefile.am b/Makefile.am index c05af3518a9..0390b833b25 100644 --- a/Makefile.am +++ b/Makefile.am @@ -103,6 +103,8 @@ libeasyrpg_player_a_SOURCES = \ src/fileext_guesser.h \ src/filesystem.cpp \ src/filesystem.h \ + src/filesystem_drive.cpp \ + src/filesystem_drive.h \ src/filesystem_lzh.cpp \ src/filesystem_lzh.h \ src/filesystem_native.cpp \ diff --git a/src/directory_tree.h b/src/directory_tree.h index 6232f5814e2..71f72ba50fb 100644 --- a/src/directory_tree.h +++ b/src/directory_tree.h @@ -43,6 +43,8 @@ class DirectoryTree { Regular, /** Directory */ Directory, + /** A virtual directory that will access a different virtual filesystem */ + Filesystem, /** Anything of no interest such as block devices */ Other }; @@ -53,8 +55,15 @@ class DirectoryTree { std::string name; /** File type */ FileType type; + /** Human readable name shown in the Game Browser (if different to the filename) */ + std::string human_name; Entry(std::string name, FileType type) : name(std::move(name)), type(type) {} + Entry(std::string name, FileType type, std::string human_name) : name(std::move(name)), type(type), human_name(std::move(human_name)) {} + + StringView GetReadableName() const { + return human_name.empty() ? name : human_name; + } }; /** Argument struct for more complex find operations */ diff --git a/src/filesystem.cpp b/src/filesystem.cpp index a6757392a65..1649512b5f6 100644 --- a/src/filesystem.cpp +++ b/src/filesystem.cpp @@ -87,9 +87,13 @@ void Filesystem::ClearCache(StringView path) const { FilesystemView Filesystem::Create(StringView path) const { // Determine the proper file system to use - // When the path doesn't exist check if the path contains a file that can - // be handled by another filesystem - if (!IsDirectory(path, true)) { + if (IsFilesystemNode(path)) { + // The support for "mounted" virtual filesystems is very limited and the only + // use right now is to delegate from DriveFilesystem to NativeFilesystem. + return CreateFromNode(path); + } else if (!IsDirectory(path, true)) { + // When the path doesn't exist check if the path contains a file that can + // be handled by another filesystem std::string dir_of_file; std::string path_prefix; std::vector components = FileFinder::SplitPath(path); @@ -175,10 +179,18 @@ FilesystemView Filesystem::Subtree(std::string sub_path) const { return FilesystemView(shared_from_this(), sub_path); } +bool Filesystem::IsFilesystemNode(StringView) const { + return false; +} + bool Filesystem::MakeDirectory(StringView, bool) const { return false; } +FilesystemView Filesystem::CreateFromNode(StringView) const { + return FilesystemView(); +} + bool Filesystem::IsValid() const { // FIXME: better way to do this? return Exists(""); @@ -307,6 +319,11 @@ bool FilesystemView::IsDirectory(StringView path, bool follow_symlinks) const { return fs->IsDirectory(MakePath(path), follow_symlinks); } +bool FilesystemView::IsFilesystemNode(StringView path) const { + assert(fs); + return fs->IsFilesystemNode(MakePath(path)); +} + bool FilesystemView::Exists(StringView path) const { assert(fs); return fs->Exists(MakePath(path)); @@ -372,6 +389,11 @@ bool FilesystemView::MakeDirectory(StringView dir, bool follow_symlinks) const { return fs->MakeDirectory(MakePath(dir), follow_symlinks); } +FilesystemView FilesystemView::CreateFromNode(StringView path) const { + assert(fs); + return fs->CreateFromNode(MakePath(path)); +} + bool FilesystemView::IsFeatureSupported(Filesystem::Feature f) const { assert(fs); return fs->IsFeatureSupported(f); @@ -393,6 +415,10 @@ FilesystemView FilesystemView::GoUp() const { auto [path, file] = FileFinder::GetPathAndFilename(GetSubPath()); + if (path == GetSubPath()) { + return fs->GetParent(); + } + return FilesystemView(fs, path); } diff --git a/src/filesystem.h b/src/filesystem.h index 736d4760161..096deca0198 100644 --- a/src/filesystem.h +++ b/src/filesystem.h @@ -217,9 +217,11 @@ class Filesystem : public std::enable_shared_from_this { /** @{ */ virtual bool IsFile(StringView path) const = 0; virtual bool IsDirectory(StringView path, bool follow_symlinks) const = 0; + virtual bool IsFilesystemNode(StringView path) const; virtual bool Exists(StringView path) const = 0; virtual int64_t GetFilesize(StringView path) const = 0; virtual bool MakeDirectory(StringView dir, bool follow_symlinks) const; + virtual FilesystemView CreateFromNode(StringView path) const; virtual bool IsFeatureSupported(Feature f) const; virtual std::string Describe() const = 0; /** @} */ @@ -369,6 +371,12 @@ class FilesystemView { */ bool IsDirectory(StringView path, bool follow_symlinks) const; + /** + * @param path Path to check + * @return True when path is pointing to a virtual filesystem + */ + bool IsFilesystemNode(StringView path) const; + /** * @param path Path to check * @return True when a file exists at the path @@ -461,6 +469,14 @@ class FilesystemView { */ bool MakeDirectory(StringView dir, bool follow_symlinks) const; + /** + * Create a filesystem view from the passed in path. + * The path must point to a virtual filesystem entry (type Filesystem). + * @param path Path to create filesystem from + * @return view pointing at the new fs + */ + FilesystemView CreateFromNode(StringView path) const; + /** * @param f Filesystem feature to check * @return true when the feature is supported. diff --git a/src/filesystem_drive.cpp b/src/filesystem_drive.cpp new file mode 100644 index 00000000000..d1cb6f08d89 --- /dev/null +++ b/src/filesystem_drive.cpp @@ -0,0 +1,118 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player 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 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player 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 EasyRPG Player. If not, see . + */ + +#include "filesystem_drive.h" +#include "filefinder.h" +#include "output.h" + +#include + +#ifdef _WIN32 +# include +# include +#endif + +namespace { +#ifdef _WIN32 + StringView drive_sep = "\\"; +#else + StringView drive_sep = "/"; +#endif +} + +DriveFilesystem::DriveFilesystem() : Filesystem("", FilesystemView()) { +#ifdef _WIN32 + std::wstring volume = L"A:\\"; + + DWORD logical_drives = GetLogicalDrives(); + for (int i = 0; i < 26; i++) { + if ((logical_drives & (1 << i)) > 0) { + DirectoryTree::Entry entry = { Utils::FromWideString(volume), DirectoryTree::FileType::Filesystem }; + + wchar_t volume_name[MAX_PATH]; + if (GetVolumeInformation(volume.c_str(), volume_name, MAX_PATH, nullptr, nullptr, nullptr, nullptr, 0) != 0) { + entry.human_name = fmt::format("{} ({})", Utils::FromWideString(volume), Utils::FromWideString(volume_name)); + } + + drives.push_back(entry); + } + volume[0]++; // Increment drive letter + } +#endif +} + +bool DriveFilesystem::HasDrives() const { + return !drives.empty(); +} + +bool DriveFilesystem::IsFile(StringView path) const { + (void)path; + return false; +} + +bool DriveFilesystem::IsDirectory(StringView path, bool) const { + return path.empty(); +} + +bool DriveFilesystem::IsFilesystemNode(StringView path) const { + for (const auto& drive: drives) { + if (drive.name == path) { + return true; + } +#ifdef _WIN32 + if (drive.name == Utils::ReplaceAll(ToString(path), "/", "\\")) { + return true; + } +#endif + } + + return false; +} + +bool DriveFilesystem::Exists(StringView path) const { + return IsDirectory(path, false) || IsFilesystemNode(path); +} + +int64_t DriveFilesystem::GetFilesize(StringView path) const { + (void)path; + return 0; +} + +FilesystemView DriveFilesystem::CreateFromNode(StringView path) const { + if (!IsFilesystemNode(path)) { + return {}; + } + + return FileFinder::Root().Create(path); +} + +std::streambuf* DriveFilesystem::CreateInputStreambuffer(StringView path, std::ios_base::openmode mode) const { + return nullptr; +} + +bool DriveFilesystem::GetDirectoryContent(StringView path, std::vector& tree) const { + if (!path.empty()) { + return false; + } + + tree = drives; + return true; +} + +std::string DriveFilesystem::Describe() const { + return "[Drive]"; +} diff --git a/src/filesystem_drive.h b/src/filesystem_drive.h new file mode 100644 index 00000000000..83985f9ee9d --- /dev/null +++ b/src/filesystem_drive.h @@ -0,0 +1,56 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player 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 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player 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 EasyRPG Player. If not, see . + */ + +#ifndef EP_FILESYSTEM_DRIVE_H +#define EP_FILESYSTEM_DRIVE_H + +#include "filesystem.h" + +/** + * A virtual filesystem that lists e.g. drive letters on Windows + */ +class DriveFilesystem : public Filesystem { +public: + /** + * Initializes a OS Filesystem on the given os path + */ + explicit DriveFilesystem(); + + /** @return Whether the current target platform has drive letters to list */ + bool HasDrives() const; + +protected: + /** + * Implementation of abstract methods + */ + /** @{ */ + bool IsFile(StringView path) const override; + bool IsDirectory(StringView path, bool follow_symlinks) const override; + bool IsFilesystemNode(StringView path) const override; + bool Exists(StringView path) const override; + int64_t GetFilesize(StringView path) const override; + FilesystemView CreateFromNode(StringView path) const override; + std::streambuf* CreateInputStreambuffer(StringView path, std::ios_base::openmode mode) const override; + bool GetDirectoryContent(StringView path, std::vector& entries) const override; + std::string Describe() const override; + /** @} */ + +private: + std::vector drives; +}; + +#endif diff --git a/src/filesystem_root.cpp b/src/filesystem_root.cpp index 270c6efa7f9..4270cfcd477 100644 --- a/src/filesystem_root.cpp +++ b/src/filesystem_root.cpp @@ -16,6 +16,7 @@ */ #include "filesystem_root.h" +#include "filesystem_drive.h" #include "output.h" #if defined(__ANDROID__) && !defined(USE_LIBRETRO) @@ -28,12 +29,20 @@ constexpr const StringView root_ns = "root://"; RootFilesystem::RootFilesystem() : Filesystem("", FilesystemView()) { // Add platform specific namespaces here #if defined(__ANDROID__) && !defined(USE_LIBRETRO) - fs_list.push_back(std::make_pair("apk", std::make_unique())); - fs_list.push_back(std::make_pair("content", std::make_unique("", FilesystemView()))); + fs_list.push_back(std::make_pair("apk", std::make_shared())); + fs_list.push_back(std::make_pair("content", std::make_shared("", FilesystemView()))); #endif + // Support for drive letters on e.g. Windows (and similiar concepts on other platforms) + auto drive_fs = std::make_shared(); + FilesystemView drive_view; + if (drive_fs->HasDrives()) { + drive_view = *drive_fs; + fs_list.push_back(std::make_pair("drive", drive_fs)); + } + // IMPORTANT: This must be the last filesystem in the list, do not push anything to fs_list afterwards! - fs_list.push_back(std::make_pair("file", std::make_unique("", FilesystemView()))); + fs_list.push_back(std::make_pair("file", std::make_shared("", drive_view))); assert(fs_list.back().first == "file" && "File namespace must be last!"); } diff --git a/src/scene_gamebrowser.cpp b/src/scene_gamebrowser.cpp index 06b731b8f51..38562e49cd0 100644 --- a/src/scene_gamebrowser.cpp +++ b/src/scene_gamebrowser.cpp @@ -194,9 +194,7 @@ void Scene_GameBrowser::BootGame() { return; } - FilesystemView fs; - std::string entry; - std::tie(fs, entry) = gamelist_window->GetGameFilesystem(); + FilesystemView fs = gamelist_window->GetGameFilesystem(); if (!fs) { Output::Warning("The selected file or directory cannot be opened"); diff --git a/src/window_gamelist.cpp b/src/window_gamelist.cpp index 7a0d2c7ce04..35c89d7af49 100644 --- a/src/window_gamelist.cpp +++ b/src/window_gamelist.cpp @@ -55,21 +55,21 @@ bool Window_GameList::Refresh(FilesystemView filesystem_base, bool show_dotdot) } if (dir.second.type == DirectoryTree::FileType::Regular) { if (FileFinder::IsSupportedArchiveExtension(dir.second.name)) { - game_directories.emplace_back(dir.second.name); + game_directories.emplace_back(dir.second); } - } else if (dir.second.type == DirectoryTree::FileType::Directory) { - game_directories.emplace_back(dir.second.name); + } else if (dir.second.type == DirectoryTree::FileType::Directory || dir.second.type == DirectoryTree::FileType::Filesystem) { + game_directories.emplace_back(dir.second); } } // Sort game list in place std::sort(game_directories.begin(), game_directories.end(), - [](const std::string& s, const std::string& s2) { - return strcmp(Utils::LowerCase(s).c_str(), Utils::LowerCase(s2).c_str()) <= 0; + [](DirectoryTree::Entry& s, DirectoryTree::Entry& s2) { + return strcmp(Utils::LowerCase(s.GetReadableName()).c_str(), Utils::LowerCase(s2.GetReadableName()).c_str()) <= 0; }); if (show_dotdot) { - game_directories.insert(game_directories.begin(), ".."); + game_directories.insert(game_directories.begin(), { "..", DirectoryTree::FileType::Directory }); } if (HasValidEntry()) { @@ -102,13 +102,13 @@ void Window_GameList::DrawItem(int index) { Rect rect = GetItemRect(index); contents->ClearRect(rect); - std::string text; + StringView text; if (HasValidEntry()) { - text = game_directories[index]; + text = game_directories[index].GetReadableName(); } - contents->TextDraw(rect.x, rect.y, Font::ColorDefault, game_directories[index]); + contents->TextDraw(rect.x, rect.y, Font::ColorDefault, text); } #ifdef HAVE_LHASA @@ -160,6 +160,6 @@ bool Window_GameList::HasValidEntry() { return game_directories.size() > minval; } -std::pair Window_GameList::GetGameFilesystem() const { - return { base_fs.Create(game_directories[GetIndex()]), game_directories[GetIndex()] }; +FilesystemView Window_GameList::GetGameFilesystem() const { + return base_fs.Create(game_directories[GetIndex()].name); } diff --git a/src/window_gamelist.h b/src/window_gamelist.h index a3809c6f860..4e27c475950 100644 --- a/src/window_gamelist.h +++ b/src/window_gamelist.h @@ -55,13 +55,13 @@ class Window_GameList : public Window_Selectable { bool HasValidEntry(); /** - * @return filesystem and entry name of the selected game + * @return filesystem of the selected game */ - std::pair GetGameFilesystem() const; + FilesystemView GetGameFilesystem() const; private: FilesystemView base_fs; - std::vector game_directories; + std::vector game_directories; bool show_dotdot = false; };