add support for mounting stdio (hdd) in the file browser.

This commit is contained in:
ITotalJustice
2025-05-23 12:23:28 +01:00
parent 8070268d2a
commit 93c38da742
6 changed files with 485 additions and 112 deletions

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <switch.h> #include <switch.h>
#include <dirent.h>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include <string> #include <string>
@@ -169,6 +170,26 @@ static_assert(FsPath::TestFrom(std::string_view{"abc"}));
static_assert(FsPath::TestFrom(std::string{"abc"})); static_assert(FsPath::TestFrom(std::string{"abc"}));
static_assert(FsPath::TestFrom(FsPath{"abc"})); static_assert(FsPath::TestFrom(FsPath{"abc"}));
// fwd
struct Fs;
struct File {
fs::Fs* m_fs;
FsFile m_native;
std::FILE* m_stdio;
s64 m_stdio_off;
// sadly, fatfs doesn't support fstat, so we have to manually
// stat the file to get it's size.
FsPath m_path;
};
struct Dir {
fs::Fs* m_fs;
FsDir m_native;
DIR* m_stdio;
u32 m_mode;
};
FsPath AppendPath(const fs::FsPath& root_path, const fs::FsPath& file_path); FsPath AppendPath(const fs::FsPath& root_path, const fs::FsPath& file_path);
Result CreateFile(FsFileSystem* fs, const FsPath& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true); Result CreateFile(FsFileSystem* fs, const FsPath& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true);
@@ -205,6 +226,24 @@ Result read_entire_file(const FsPath& path, std::vector<u8>& out);
Result write_entire_file(const FsPath& path, const std::vector<u8>& in, bool ignore_read_only = true); Result write_entire_file(const FsPath& path, const std::vector<u8>& in, bool ignore_read_only = true);
Result copy_entire_file(const FsPath& dst, const FsPath& src, bool ignore_read_only = true); Result copy_entire_file(const FsPath& dst, const FsPath& src, bool ignore_read_only = true);
Result OpenFile(fs::Fs* fs, const fs::FsPath& path, u32 mode, File* f);
Result FileRead(File* f, s64 off, void* buf, u64 read_size, u32 option, u64* bytes_read);
Result FileWrite(File* f, s64 off, const void* buf, u64 write_size, u32 option);
Result FileSetSize(File* f, s64 sz);
Result FileGetSize(File* f, s64* out);
void FileClose(File* f);
Result OpenDirectory(fs::Fs* fs, const fs::FsPath& path, u32 mode, Dir* d);
Result DirReadAll(Dir* d, std::vector<FsDirectoryEntry>& buf);
void DirClose(Dir* d);
// optimised for stdio calls as stat returns size and timestamp in a single call.
// whereas for native, this is 2 function calls.
// however if you need both, you will need 2 calls for native anyway,
// but can avoid the second (expensive) stat call.
Result FileGetSizeAndTimestamp(fs::Fs* fs, const FsPath& path, FsTimeStampRaw* ts, s64* size);
Result IsDirEmpty(fs::Fs* m_fs, const fs::FsPath& path, bool* out);
struct Fs { struct Fs {
static constexpr inline u32 FsModule = 505; static constexpr inline u32 FsModule = 505;
static constexpr inline Result ResultTooManyEntries = MAKERESULT(FsModule, 1); static constexpr inline Result ResultTooManyEntries = MAKERESULT(FsModule, 1);
@@ -238,10 +277,48 @@ struct Fs {
virtual Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out) = 0; virtual Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out) = 0;
virtual bool FileExists(const FsPath& path) = 0; virtual bool FileExists(const FsPath& path) = 0;
virtual bool DirExists(const FsPath& path) = 0; virtual bool DirExists(const FsPath& path) = 0;
virtual bool IsNative() const = 0;
virtual FsPath Root() const { return "/"; }
virtual Result read_entire_file(const FsPath& path, std::vector<u8>& out) = 0; virtual Result read_entire_file(const FsPath& path, std::vector<u8>& out) = 0;
virtual Result write_entire_file(const FsPath& path, const std::vector<u8>& in) = 0; virtual Result write_entire_file(const FsPath& path, const std::vector<u8>& in) = 0;
virtual Result copy_entire_file(const FsPath& dst, const FsPath& src) = 0; virtual Result copy_entire_file(const FsPath& dst, const FsPath& src) = 0;
Result OpenFile(const fs::FsPath& path, u32 mode, File* f) {
return fs::OpenFile(this, path, mode, f);
}
Result FileRead(File* f, s64 off, void* buf, u64 read_size, u32 option, u64* bytes_read) {
return fs::FileRead(f, off, buf, read_size, option, bytes_read);
}
Result FileWrite(File* f, s64 off, const void* buf, u64 write_size, u32 option) {
return fs::FileWrite(f, off, buf, write_size, option);
}
Result FileSetSize(File* f, s64 sz) {
return fs::FileSetSize(f, sz);
}
Result FileGetSize(File* f, s64* out) {
return fs::FileGetSize(f, out);
}
void FileClose(File* f) {
fs::FileClose(f);
}
Result OpenDirectory(const fs::FsPath& path, u32 mode, Dir* d) {
return fs::OpenDirectory(this, path, mode, d);
}
Result DirReadAll(Dir* d, std::vector<FsDirectoryEntry>& buf) {
return fs::DirReadAll(d, buf);
}
void DirClose(Dir* d) {
fs::DirClose(d);
}
Result FileGetSizeAndTimestamp(const FsPath& path, FsTimeStampRaw* ts, s64* size) {
return fs::FileGetSizeAndTimestamp(this, path, ts, size);
}
Result IsDirEmpty(const fs::FsPath& path, bool* out) {
return fs::IsDirEmpty(this, path, out);
}
void SetIgnoreReadOnly(bool enable) { void SetIgnoreReadOnly(bool enable) {
m_ignore_read_only = enable; m_ignore_read_only = enable;
} }
@@ -251,7 +328,7 @@ protected:
}; };
struct FsStdio : Fs { struct FsStdio : Fs {
FsStdio(bool ignore_read_only = true) : Fs{ignore_read_only} {} FsStdio(bool ignore_read_only = true, const FsPath& root = "/") : Fs{ignore_read_only}, m_root{root} {}
virtual ~FsStdio() = default; virtual ~FsStdio() = default;
Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0) override { Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0) override {
@@ -293,6 +370,12 @@ struct FsStdio : Fs {
bool DirExists(const FsPath& path) override { bool DirExists(const FsPath& path) override {
return fs::DirExists(path); return fs::DirExists(path);
} }
bool IsNative() const override {
return false;
}
FsPath Root() const override {
return m_root;
}
Result read_entire_file(const FsPath& path, std::vector<u8>& out) override { Result read_entire_file(const FsPath& path, std::vector<u8>& out) override {
return fs::read_entire_file(path, out); return fs::read_entire_file(path, out);
} }
@@ -302,6 +385,8 @@ struct FsStdio : Fs {
Result copy_entire_file(const FsPath& dst, const FsPath& src) override { Result copy_entire_file(const FsPath& dst, const FsPath& src) override {
return fs::copy_entire_file(dst, src, m_ignore_read_only); return fs::copy_entire_file(dst, src, m_ignore_read_only);
} }
const FsPath m_root;
}; };
struct FsNative : Fs { struct FsNative : Fs {
@@ -407,6 +492,9 @@ struct FsNative : Fs {
bool DirExists(const FsPath& path) override { bool DirExists(const FsPath& path) override {
return fs::DirExists(&m_fs, path); return fs::DirExists(&m_fs, path);
} }
bool IsNative() const override {
return true;
}
Result read_entire_file(const FsPath& path, std::vector<u8>& out) override { Result read_entire_file(const FsPath& path, std::vector<u8>& out) override {
return fs::read_entire_file(&m_fs, path, out); return fs::read_entire_file(&m_fs, path, out);
} }

View File

@@ -10,10 +10,19 @@
namespace sphaira::ui::menu::filebrowser { namespace sphaira::ui::menu::filebrowser {
enum FsEntryFlag {
FsEntryFlag_None,
// write protected.
FsEntryFlag_ReadOnly = 1 << 0,
// supports file assoc.
FsEntryFlag_Assoc = 1 << 1,
};
enum class FsType { enum class FsType {
Sd, Sd,
ImageNand, ImageNand,
ImageSd, ImageSd,
Stdio,
}; };
enum class SelectedType { enum class SelectedType {
@@ -33,6 +42,21 @@ enum OrderType {
OrderType_Ascending, OrderType_Ascending,
}; };
struct FsEntry {
fs::FsPath name{};
fs::FsPath root{};
FsType type{};
u32 flags{FsEntryFlag_None};
auto IsReadOnly() const -> bool {
return flags & FsEntryFlag_ReadOnly;
}
auto IsAssoc() const -> bool {
return flags & FsEntryFlag_Assoc;
}
};
// roughly 1kib in size per entry // roughly 1kib in size per entry
struct FileEntry : FsDirectoryEntry { struct FileEntry : FsDirectoryEntry {
std::string extension{}; // if any std::string extension{}; // if any
@@ -225,6 +249,10 @@ private:
return GetEntry(m_index); return GetEntry(m_index);
} }
auto IsSd() const -> bool {
return m_fs_entry.type == FsType::Sd;
}
void Sort(); void Sort();
void SortAndFindLastFile(); void SortAndFindLastFile();
void SetIndexFromLastFile(const LastFile& last_file); void SetIndexFromLastFile(const LastFile& last_file);
@@ -238,14 +266,18 @@ private:
auto get_collection(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result; auto get_collection(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result; auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
void SetFs(const fs::FsPath& new_path, u32 new_type); void SetFs(const fs::FsPath& new_path, const FsEntry& new_entry);
auto GetNative() -> fs::FsNative* {
return (fs::FsNative*)m_fs.get();
}
private: private:
static constexpr inline const char* INI_SECTION = "filebrowser"; static constexpr inline const char* INI_SECTION = "filebrowser";
const std::vector<NroEntry>& m_nro_entries; const std::vector<NroEntry>& m_nro_entries;
std::unique_ptr<fs::FsNative> m_fs{}; std::unique_ptr<fs::Fs> m_fs{};
FsType m_fs_type{}; FsEntry m_fs_entry{};
fs::FsPath m_path{}; fs::FsPath m_path{};
std::vector<FileEntry> m_entries{}; std::vector<FileEntry> m_entries{};
std::vector<u32> m_entries_index{}; // files not including hidden std::vector<u32> m_entries_index{}; // files not including hidden
@@ -280,7 +312,6 @@ private:
option::OptionBool m_folders_first{INI_SECTION, "folders_first", true}; option::OptionBool m_folders_first{INI_SECTION, "folders_first", true};
option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false}; option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false};
option::OptionBool m_ignore_read_only{INI_SECTION, "ignore_read_only", false}; option::OptionBool m_ignore_read_only{INI_SECTION, "ignore_read_only", false};
option::OptionLong m_mount{INI_SECTION, "mount", 0};
bool m_loaded_assoc_entries{}; bool m_loaded_assoc_entries{};
bool m_is_update_folder{}; bool m_is_update_folder{};

View File

@@ -36,6 +36,7 @@ struct ProgressBox final : Widget {
auto ShouldExitResult() -> Result; auto ShouldExitResult() -> Result;
// helper functions // helper functions
auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst) -> Result;
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result; auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result;
void Yield(); void Yield();

View File

@@ -121,12 +121,18 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig
auto path_view = std::string_view{_path}; auto path_view = std::string_view{_path};
// todo: fix this for sdmc: and ums0: // todo: fix this for sdmc: and ums0:
FsPath path{"/"}; FsPath path{"/"};
if (auto s = std::strchr(_path.s, ':')) {
const int len = (s - _path.s) + 1;
std::snprintf(path, sizeof(path), "%.*s/", len, _path.s);
path_view = path_view.substr(len);
}
for (const auto dir : std::views::split(path_view, '/')) { for (const auto dir : std::views::split(path_view, '/')) {
if (dir.empty()) { if (dir.empty()) {
continue; continue;
} }
std::strncat(path, dir.data(), dir.size()); std::strncat(path, dir.data(), dir.size());
log_write("[FS] dir creation path is now: %s\n", path.s);
Result rc; Result rc;
if (fs) { if (fs) {
@@ -447,4 +453,233 @@ Result copy_entire_file(const FsPath& dst, const FsPath& src, bool ignore_read_o
return write_entire_file(dst, data, ignore_read_only); return write_entire_file(dst, data, ignore_read_only);
} }
Result OpenFile(fs::Fs* fs, const fs::FsPath& path, u32 mode, File* f) {
*f = {};
f->m_fs = fs;
if (f->m_fs->IsNative()) {
auto fs = (fs::FsNative*)f->m_fs;
R_TRY(fs->OpenFile(path, mode, &f->m_native));
} else {
if ((mode & FsOpenMode_Read) && (mode & FsOpenMode_Write)) {
// todo:
R_THROW(0x1);
} else if (mode & FsOpenMode_Read) {
f->m_stdio = fopen(path, "rb");
} else if (mode & FsOpenMode_Write) {
f->m_stdio = fopen(path, "wb");
}
R_UNLESS(f->m_stdio, 0x1);
}
std::strcpy(f->m_path, path);
R_SUCCEED();
}
Result FileRead(File* f, s64 off, void* buf, u64 read_size, u32 option, u64* bytes_read) {
if (f->m_fs->IsNative()) {
R_TRY(fsFileRead(&f->m_native, off, buf, read_size, option, bytes_read));
} else {
if (f->m_stdio_off != off) {
f->m_stdio_off = off;
std::fseek(f->m_stdio, off, SEEK_SET);
}
*bytes_read = std::fread(buf, 1, read_size, f->m_stdio);
f->m_stdio_off += *bytes_read;
}
R_SUCCEED();
}
Result FileWrite(File* f, s64 off, const void* buf, u64 write_size, u32 option) {
if (f->m_fs->IsNative()) {
R_TRY(fsFileWrite(&f->m_native, off, buf, write_size, option));
} else {
if (f->m_stdio_off != off) {
log_write("[FS] diff seek\n");
f->m_stdio_off = off;
std::fseek(f->m_stdio, off, SEEK_SET);
}
const auto result = std::fwrite(buf, 1, write_size, f->m_stdio);
// log_write("[FS] fwrite res: %zu vs %zu\n", result, write_size);
R_UNLESS(result == write_size, 0x1);
f->m_stdio_off += write_size;
}
R_SUCCEED();
}
Result FileSetSize(File* f, s64 sz) {
if (f->m_fs->IsNative()) {
R_TRY(fsFileSetSize(&f->m_native, sz));
} else {
R_SUCCEED();
// const auto fd = fileno(f->m_stdio);
// R_UNLESS(fd > 0, 0x1);
// R_UNLESS(!ftruncate(fd, sz), 0x1);
}
R_SUCCEED();
}
Result FileGetSize(File* f, s64* out) {
if (f->m_fs->IsNative()) {
R_TRY(fsFileGetSize(&f->m_native, out));
} else {
struct stat st;
const auto fd = fileno(f->m_stdio);
bool did_stat{};
if (fd && !fstat(fd, &st)) {
did_stat = true;
}
if (!did_stat) {
R_UNLESS(!lstat(f->m_path, &st), 0x1);
}
*out = st.st_size;
}
R_SUCCEED();
}
void FileClose(File* f) {
if (f->m_fs->IsNative()) {
fsFileClose(&f->m_native);
} else {
std::fclose(f->m_stdio);
}
*f = {};
}
Result OpenDirectory(fs::Fs* fs, const fs::FsPath& path, u32 mode, Dir* d) {
*d = {};
d->m_fs = fs;
d->m_mode = mode;
if (d->m_fs->IsNative()) {
auto fs = (fs::FsNative*)d->m_fs;
R_TRY(fs->OpenDirectory(path, mode, &d->m_native));
} else {
d->m_stdio = opendir(path);
R_UNLESS(d->m_stdio, 0x1);
}
R_SUCCEED();
}
Result DirReadAll(Dir* d, std::vector<FsDirectoryEntry>& buf) {
buf.clear();
if (d->m_fs->IsNative()) {
auto fs = (fs::FsNative*)d->m_fs;
s64 count;
R_TRY(fs->DirGetEntryCount(&d->m_native, &count));
buf.resize(count);
R_TRY(fs->DirRead(&d->m_native, &count, buf.size(), buf.data()));
buf.resize(count);
} else {
buf.reserve(1000);
struct dirent* dirent;
while ((dirent = readdir(d->m_stdio))) {
if (!std::strcmp(dirent->d_name, ".") || !std::strcmp(dirent->d_name, "..")) {
continue;
}
FsDirectoryEntry entry{};
if (dirent->d_type == DT_DIR) {
if (!(d->m_mode & FsDirOpenMode_ReadDirs)) {
continue;
}
entry.type = FsDirEntryType_Dir;
} else if (dirent->d_type == DT_REG) {
if (!(d->m_mode & FsDirOpenMode_ReadFiles)) {
continue;
}
entry.type = FsDirEntryType_File;
}
std::strcpy(entry.name, dirent->d_name);
buf.emplace_back(entry);
}
}
R_SUCCEED();
}
void DirClose(Dir* d) {
if (d->m_fs->IsNative()) {
fsDirClose(&d->m_native);
} else {
closedir(d->m_stdio);
}
*d = {};
}
Result FileGetSizeAndTimestamp(fs::Fs* m_fs, const FsPath& path, FsTimeStampRaw* ts, s64* size) {
*ts = {};
*size = {};
if (m_fs->IsNative()) {
auto fs = (fs::FsNative*)m_fs;
R_TRY(fs->GetFileTimeStampRaw(path, ts));
File f;
R_TRY(m_fs->OpenFile(path, FsOpenMode_Read, &f));
ON_SCOPE_EXIT(fs->FileClose(&f));
R_TRY(m_fs->FileGetSize(&f, size));
} else {
struct stat st;
R_UNLESS(!lstat(path, &st), 0x1);
ts->is_valid = true;
ts->created = st.st_ctim.tv_sec;
ts->modified = st.st_mtim.tv_sec;
ts->accessed = st.st_atim.tv_sec;
*size = st.st_size;
}
R_SUCCEED();
}
Result IsDirEmpty(fs::Fs* m_fs, const fs::FsPath& path, bool* out) {
*out = true;
if (m_fs->IsNative()) {
auto fs = (fs::FsNative*)m_fs;
s64 count;
R_TRY(fs->DirGetEntryCount(path, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles | FsDirOpenMode_NoFileSize, &count));
*out = !count;
} else {
auto dir = opendir(path);
R_UNLESS(dir, 0x1);
ON_SCOPE_EXIT(closedir(dir));
struct dirent* dirent;
while ((dirent = readdir(dir))) {
if (!std::strcmp(dirent->d_name, ".") || !std::strcmp(dirent->d_name, "..")) {
continue;
}
*out = false;
break;
}
}
R_SUCCEED();
}
} // namespace fs } // namespace fs

View File

@@ -42,6 +42,16 @@
namespace sphaira::ui::menu::filebrowser { namespace sphaira::ui::menu::filebrowser {
namespace { namespace {
constexpr FsEntry FS_ENTRY_DEFAULT{
"Sd", "/", FsType::Sd, FsEntryFlag_Assoc,
};
constexpr FsEntry FS_ENTRIES[]{
FS_ENTRY_DEFAULT,
{ "Image System memory", "/", FsType::ImageNand },
{ "Image microSD card", "/", FsType::ImageSd},
};
struct ExtDbEntry { struct ExtDbEntry {
std::string_view db_name; std::string_view db_name;
std::span<const std::string_view> ext; std::span<const std::string_view> ext;
@@ -198,7 +208,7 @@ auto GetRomDatabaseFromPath(std::string_view path) -> RomDatabaseIndexs {
} }
// //
auto GetRomIcon(fs::FsNative* fs, ProgressBox* pbox, std::string filename, const RomDatabaseIndexs& db_indexs, const NroEntry& nro) { auto GetRomIcon(fs::Fs* fs, ProgressBox* pbox, std::string filename, const RomDatabaseIndexs& db_indexs, const NroEntry& nro) {
// if no db entries, use nro icon // if no db entries, use nro icon
if (db_indexs.empty()) { if (db_indexs.empty()) {
log_write("using nro image\n"); log_write("using nro image\n");
@@ -315,7 +325,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
return; return;
} }
if (m_fs_type == FsType::Sd && m_is_update_folder && m_daybreak_path.has_value()) { if (IsSd() && m_is_update_folder && m_daybreak_path.has_value()) {
App::Push(std::make_shared<OptionBox>("Open with DayBreak?"_i18n, "No"_i18n, "Yes"_i18n, 1, [this](auto op_index){ App::Push(std::make_shared<OptionBox>("Open with DayBreak?"_i18n, "No"_i18n, "Yes"_i18n, 1, [this](auto op_index){
if (op_index && *op_index) { if (op_index && *op_index) {
// daybreak uses native fs so do not use nro_add_arg_file // daybreak uses native fs so do not use nro_add_arg_file
@@ -330,9 +340,9 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
if (entry.type == FsDirEntryType_Dir) { if (entry.type == FsDirEntryType_Dir) {
Scan(GetNewPathCurrent()); Scan(GetNewPathCurrent());
} else if (m_fs_type == FsType::Sd) { } else {
// special case for nro // special case for nro
if (entry.GetExtension() == "nro") { if (IsSd() && entry.GetExtension() == "nro") {
App::Push(std::make_shared<OptionBox>("Launch "_i18n + entry.GetName() + '?', App::Push(std::make_shared<OptionBox>("Launch "_i18n + entry.GetName() + '?',
"No"_i18n, "Launch"_i18n, 1, [this](auto op_index){ "No"_i18n, "Launch"_i18n, 1, [this](auto op_index){
if (op_index && *op_index) { if (op_index && *op_index) {
@@ -341,7 +351,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
})); }));
} else if (App::GetInstallEnable() && IsExtension(entry.GetExtension(), INSTALL_EXTENSIONS)) { } else if (App::GetInstallEnable() && IsExtension(entry.GetExtension(), INSTALL_EXTENSIONS)) {
InstallFiles(); InstallFiles();
} else { } else if (IsSd()) {
const auto assoc_list = FindFileAssocFor(); const auto assoc_list = FindFileAssocFor();
if (!assoc_list.empty()) { if (!assoc_list.empty()) {
// for (auto&e : assoc_list) { // for (auto&e : assoc_list) {
@@ -374,12 +384,12 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
std::make_pair(Button::B, Action{"Back"_i18n, [this](){ std::make_pair(Button::B, Action{"Back"_i18n, [this](){
std::string_view view{m_path}; std::string_view view{m_path};
if (view != "/") { if (view != m_fs->Root()) {
const auto end = view.find_last_of('/'); const auto end = view.find_last_of('/');
assert(end != view.npos); assert(end != view.npos);
if (end == 0) { if (end == 0) {
Scan("/", true); Scan(m_fs->Root(), true);
} else { } else {
Scan(view.substr(0, end), true); Scan(view.substr(0, end), true);
} }
@@ -519,7 +529,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
} }
} }
if (m_fs_type == FsType::Sd && m_entries_current.size()) { if (IsSd() && m_entries_current.size()) {
if (App::GetInstallEnable() && HasTypeInSelectedEntries(FsDirEntryType_File) && !m_selected_count && (GetEntry().GetExtension() == "nro" || !FindFileAssocFor().empty())) { if (App::GetInstallEnable() && HasTypeInSelectedEntries(FsDirEntryType_File) && !m_selected_count && (GetEntry().GetExtension() == "nro" || !FindFileAssocFor().empty())) {
options->Add(std::make_shared<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){; options->Add(std::make_shared<SidebarEntryCallback>("Install Forwarder"_i18n, [this](){;
if (App::GetInstallPrompt()) { if (App::GetInstallPrompt()) {
@@ -552,7 +562,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
App::Push(std::make_shared<OptionBox>("Are you sure you want to extract to root?"_i18n, App::Push(std::make_shared<OptionBox>("Are you sure you want to extract to root?"_i18n,
"No"_i18n, "Yes"_i18n, 0, [this](auto op_index){ "No"_i18n, "Yes"_i18n, 0, [this](auto op_index){
if (op_index && *op_index) { if (op_index && *op_index) {
UnzipFiles("/"); UnzipFiles(m_fs->Root());
} }
})); }));
})); }));
@@ -591,11 +601,11 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
options->Add(std::make_shared<SidebarEntryCallback>("Create File"_i18n, [this](){ options->Add(std::make_shared<SidebarEntryCallback>("Create File"_i18n, [this](){
std::string out; std::string out;
if (R_SUCCEEDED(swkbd::ShowText(out, "Set File Name"_i18n.c_str())) && !out.empty()) { if (R_SUCCEEDED(swkbd::ShowText(out, "Set File Name"_i18n.c_str(), fs::AppendPath(m_path, ""))) && !out.empty()) {
App::PopToMenu(); App::PopToMenu();
fs::FsPath full_path; fs::FsPath full_path;
if (out[0] == '/') { if (out.starts_with(m_fs_entry.root.s)) {
full_path = out; full_path = out;
} else { } else {
full_path = fs::AppendPath(m_path, out); full_path = fs::AppendPath(m_path, out);
@@ -617,7 +627,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
App::PopToMenu(); App::PopToMenu();
fs::FsPath full_path; fs::FsPath full_path;
if (out[0] == '/') { if (out.starts_with(m_fs_entry.root.s)) {
full_path = out; full_path = out;
} else { } else {
full_path = fs::AppendPath(m_path, out); full_path = fs::AppendPath(m_path, out);
@@ -632,13 +642,13 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
} }
})); }));
if (m_fs_type == FsType::Sd && m_entries_current.size() && !m_selected_count && GetEntry().IsFile() && GetEntry().file_size < 1024*64) { if (IsSd() && m_entries_current.size() && !m_selected_count && GetEntry().IsFile() && GetEntry().file_size < 1024*64) {
options->Add(std::make_shared<SidebarEntryCallback>("View as text (unfinished)"_i18n, [this](){ options->Add(std::make_shared<SidebarEntryCallback>("View as text (unfinished)"_i18n, [this](){
App::Push(std::make_shared<fileview::Menu>(GetNewPathCurrent())); App::Push(std::make_shared<fileview::Menu>(GetNewPathCurrent()));
})); }));
} }
if (m_fs_type == FsType::Sd && m_entries_current.size()) { if (m_entries_current.size()) {
options->Add(std::make_shared<SidebarEntryCallback>("Upload"_i18n, [this](){ options->Add(std::make_shared<SidebarEntryCallback>("Upload"_i18n, [this](){
UploadFiles(); UploadFiles();
})); }));
@@ -650,15 +660,27 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
})); }));
SidebarEntryArray::Items mount_items; SidebarEntryArray::Items mount_items;
mount_items.push_back("Sd"_i18n); std::vector<FsEntry> fs_entries;
mount_items.push_back("Image System memory"_i18n); for (const auto& e: FS_ENTRIES) {
mount_items.push_back("Image microSD card"_i18n); fs_entries.emplace_back(e);
mount_items.push_back(i18n::get(e.name));
}
options->Add(std::make_shared<SidebarEntryArray>("Mount"_i18n, mount_items, [this](s64& index_out){ const auto stdio_locations = location::GetStdio(false);
for (const auto& e: stdio_locations) {
u32 flags{};
if (e.write_protect) {
flags |= FsEntryFlag_ReadOnly;
}
fs_entries.emplace_back(e.name, e.mount, FsType::Stdio, flags);
mount_items.push_back(e.name);
}
options->Add(std::make_shared<SidebarEntryArray>("Mount"_i18n, mount_items, [this, fs_entries](s64& index_out){
App::PopToMenu(); App::PopToMenu();
m_mount.Set(index_out); SetFs(fs_entries[index_out].root, fs_entries[index_out]);
SetFs("/", index_out); }, m_fs_entry.name));
}, m_mount.Get()));
})); }));
}}) }})
); );
@@ -668,11 +690,14 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
fs::FsPath buf; fs::FsPath buf;
ini_gets("paths", "last_path", "/", buf, sizeof(buf), App::CONFIG_PATH); ini_gets("paths", "last_path", "/", buf, sizeof(buf), App::CONFIG_PATH);
SetFs(buf, m_mount.Get()); SetFs(buf, FS_ENTRY_DEFAULT);
} }
Menu::~Menu() { Menu::~Menu() {
// don't store mount points for non-sd card paths.
if (IsSd()) {
ini_puts("paths", "last_path", m_path, App::CONFIG_PATH); ini_puts("paths", "last_path", m_path, App::CONFIG_PATH);
}
} }
void Menu::Update(Controller* controller, TouchInfo* touch) { void Menu::Update(Controller* controller, TouchInfo* touch) {
@@ -704,10 +729,10 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
auto& e = GetEntry(i); auto& e = GetEntry(i);
if (e.IsDir()) { if (e.IsDir()) {
if (e.file_count == -1 && e.dir_count == -1) { if (m_fs->IsNative() && e.file_count == -1 && e.dir_count == -1) {
const auto full_path = GetNewPath(e); const auto full_path = GetNewPath(e);
m_fs->DirGetEntryCount(full_path, FsDirOpenMode_ReadFiles | FsDirOpenMode_NoFileSize, &e.file_count); GetNative()->DirGetEntryCount(full_path, FsDirOpenMode_ReadFiles | FsDirOpenMode_NoFileSize, &e.file_count);
m_fs->DirGetEntryCount(full_path, FsDirOpenMode_ReadDirs | FsDirOpenMode_NoFileSize, &e.dir_count); GetNative()->DirGetEntryCount(full_path, FsDirOpenMode_ReadDirs | FsDirOpenMode_NoFileSize, &e.dir_count);
} }
} else if (!e.checked_extension) { } else if (!e.checked_extension) {
e.checked_extension = true; e.checked_extension = true;
@@ -759,13 +784,17 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
gfx::drawText(vg, x + text_xoffset+65, y + (h / 2.f), 20.f, e.name, NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(text_id)); gfx::drawText(vg, x + text_xoffset+65, y + (h / 2.f), 20.f, e.name, NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(text_id));
nvgRestore(vg); nvgRestore(vg);
if (e.IsDir()) { if (m_fs->IsNative() && e.IsDir()) {
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%zd files"_i18n.c_str(), e.file_count); gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%zd files"_i18n.c_str(), e.file_count);
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->GetColour(text_id), "%zd dirs"_i18n.c_str(), e.dir_count); gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->GetColour(text_id), "%zd dirs"_i18n.c_str(), e.dir_count);
} else { } else if (e.IsFile()) {
if (!e.time_stamp.is_valid) { if (!e.time_stamp.is_valid) {
const auto path = GetNewPath(e); const auto path = GetNewPath(e);
if (m_fs->IsNative()) {
m_fs->GetFileTimeStampRaw(path, &e.time_stamp); m_fs->GetFileTimeStampRaw(path, &e.time_stamp);
} else {
m_fs->FileGetSizeAndTimestamp(path, &e.time_stamp, &e.file_size);
}
} }
const auto t = (time_t)(e.time_stamp.modified); const auto t = (time_t)(e.time_stamp.modified);
struct tm tm{}; struct tm tm{};
@@ -784,7 +813,7 @@ void Menu::OnFocusGained() {
MenuBase::OnFocusGained(); MenuBase::OnFocusGained();
if (m_entries.empty()) { if (m_entries.empty()) {
if (m_path.empty()) { if (m_path.empty()) {
Scan("/"); Scan(m_fs->Root());
} else { } else {
Scan(m_path); Scan(m_path);
} }
@@ -899,7 +928,11 @@ void Menu::InstallFiles() {
App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this, targets](auto pbox) -> Result { App::Push(std::make_shared<ui::ProgressBox>(0, "Installing "_i18n, "", [this, targets](auto pbox) -> Result {
for (auto& e : targets) { for (auto& e : targets) {
R_TRY(yati::InstallFromFile(pbox, &m_fs->m_fs, GetNewPath(e))); if (m_fs->IsNative()) {
R_TRY(yati::InstallFromFile(pbox, &GetNative()->m_fs, GetNewPath(e)));
} else {
R_TRY(yati::InstallFromStdioFile(pbox, GetNewPath(e)));
}
App::Notify("Installed " + e.GetName()); App::Notify("Installed " + e.GetName());
} }
@@ -919,7 +952,6 @@ void Menu::UnzipFiles(fs::FsPath dir_path) {
App::Push(std::make_shared<ui::ProgressBox>(0, "Extracting "_i18n, "", [this, dir_path, targets](auto pbox) -> Result { App::Push(std::make_shared<ui::ProgressBox>(0, "Extracting "_i18n, "", [this, dir_path, targets](auto pbox) -> Result {
constexpr auto chunk_size = 1024 * 512; // 512KiB constexpr auto chunk_size = 1024 * 512; // 512KiB
auto& fs = *m_fs.get();
for (auto& e : targets) { for (auto& e : targets) {
pbox->SetTitle(e.GetName()); pbox->SetTitle(e.GetName());
@@ -959,19 +991,19 @@ void Menu::UnzipFiles(fs::FsPath dir_path) {
pbox->NewTransfer(name); pbox->NewTransfer(name);
// create directories // create directories
fs.CreateDirectoryRecursivelyWithPath(file_path); m_fs->CreateDirectoryRecursivelyWithPath(file_path);
Result rc; Result rc;
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc = m_fs->CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create file: %s 0x%04X\n", file_path.s, rc); log_write("failed to create file: %s 0x%04X\n", file_path.s, rc);
R_THROW(rc); R_THROW(rc);
} }
FsFile f; fs::File f;
R_TRY(fs.OpenFile(file_path, FsOpenMode_Write, &f)); R_TRY(m_fs->OpenFile(file_path, FsOpenMode_Write, &f));
ON_SCOPE_EXIT(fsFileClose(&f)); ON_SCOPE_EXIT(m_fs->FileClose(&f));
R_TRY(fsFileSetSize(&f, info.uncompressed_size)); R_TRY(m_fs->FileSetSize(&f, info.uncompressed_size));
std::vector<char> buf(chunk_size); std::vector<char> buf(chunk_size);
s64 offset{}; s64 offset{};
@@ -984,7 +1016,7 @@ void Menu::UnzipFiles(fs::FsPath dir_path) {
R_THROW(0x1); R_THROW(0x1);
} }
R_TRY(fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None)); R_TRY(m_fs->FileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None));
pbox->UpdateTransfer(offset, info.uncompressed_size); pbox->UpdateTransfer(offset, info.uncompressed_size);
offset += bytes_read; offset += bytes_read;
@@ -1030,7 +1062,7 @@ void Menu::ZipFiles(fs::FsPath zip_out) {
} }
zip_out = fs::AppendPath(m_path, file_path); zip_out = fs::AppendPath(m_path, file_path);
if (!fs::FileExists(&m_fs->m_fs, zip_out)) { if (!m_fs->FileExists(zip_out)) {
break; break;
} }
} }
@@ -1043,7 +1075,6 @@ void Menu::ZipFiles(fs::FsPath zip_out) {
App::Push(std::make_shared<ui::ProgressBox>(0, "Compressing "_i18n, "", [this, zip_out, targets](auto pbox) -> Result { App::Push(std::make_shared<ui::ProgressBox>(0, "Compressing "_i18n, "", [this, zip_out, targets](auto pbox) -> Result {
constexpr auto chunk_size = 1024 * 512; // 512KiB constexpr auto chunk_size = 1024 * 512; // 512KiB
auto& fs = *m_fs.get();
const auto t = std::time(NULL); const auto t = std::time(NULL);
const auto tm = std::localtime(&t); const auto tm = std::localtime(&t);
@@ -1080,12 +1111,12 @@ void Menu::ZipFiles(fs::FsPath zip_out) {
} }
ON_SCOPE_EXIT(zipCloseFileInZip(zfile)); ON_SCOPE_EXIT(zipCloseFileInZip(zfile));
FsFile f; fs::File f;
R_TRY(fs.OpenFile(file_path, FsOpenMode_Read, &f)); R_TRY(m_fs->OpenFile(file_path, FsOpenMode_Read, &f));
ON_SCOPE_EXIT(fsFileClose(&f)); ON_SCOPE_EXIT(m_fs->FileClose(&f));
s64 file_size; s64 file_size;
R_TRY(fsFileGetSize(&f, &file_size)); R_TRY(m_fs->FileGetSize(&f, &file_size));
std::vector<char> buf(chunk_size); std::vector<char> buf(chunk_size);
s64 offset{}; s64 offset{};
@@ -1093,7 +1124,7 @@ void Menu::ZipFiles(fs::FsPath zip_out) {
R_TRY(pbox->ShouldExitResult()); R_TRY(pbox->ShouldExitResult());
u64 bytes_read; u64 bytes_read;
R_TRY(fsFileRead(&f, offset, buf.data(), buf.size(), FsReadOption_None, &bytes_read)); R_TRY(m_fs->FileRead(&f, offset, buf.data(), buf.size(), FsReadOption_None, &bytes_read));
if (ZIP_OK != zipWriteInFileInZip(zfile, buf.data(), bytes_read)) { if (ZIP_OK != zipWriteInFileInZip(zfile, buf.data(), bytes_read)) {
log_write("failed to write zip file: %s\n", file_path.s); log_write("failed to write zip file: %s\n", file_path.s);
@@ -1168,13 +1199,13 @@ void Menu::UploadFiles() {
pbox->SetTitle(name); pbox->SetTitle(name);
pbox->NewTransfer(relative_file_name); pbox->NewTransfer(relative_file_name);
FsFile file; fs::File f;
R_TRY(m_fs->OpenFile(file_path, FsOpenMode_Read, &file)); R_TRY(m_fs->OpenFile(file_path, FsOpenMode_Read, &f));
ON_SCOPE_EXIT(fsFileClose(&file)); ON_SCOPE_EXIT(m_fs->FileClose(&f));
return thread::TransferPull(pbox, file_size, return thread::TransferPull(pbox, file_size,
[&](void* data, s64 off, s64 size, u64* bytes_read) -> Result { [&](void* data, s64 off, s64 size, u64* bytes_read) -> Result {
return fsFileRead(&file, off, data, size, FsReadOption_None, bytes_read); return m_fs->FileRead(&f, off, data, size, FsReadOption_None, bytes_read);
}, },
[&](thread::PullCallback pull) -> Result { [&](thread::PullCallback pull) -> Result {
s64 offset{}; s64 offset{};
@@ -1256,20 +1287,16 @@ auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result {
ResetSelection(); ResetSelection();
} }
FsDir d; fs::Dir d;
R_TRY(m_fs->OpenDirectory(new_path, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, &d)); R_TRY(m_fs->OpenDirectory(new_path, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, &d));
ON_SCOPE_EXIT(fsDirClose(&d)); ON_SCOPE_EXIT(m_fs->DirClose(&d));
s64 count;
R_TRY(m_fs->DirGetEntryCount(&d, &count));
// we won't run out of memory here (tm) // we won't run out of memory here (tm)
std::vector<FsDirectoryEntry> dir_entries(count); std::vector<FsDirectoryEntry> dir_entries;
R_TRY(m_fs->DirRead(&d, &count, dir_entries.size(), dir_entries.data())); R_TRY(m_fs->DirReadAll(&d, dir_entries));
// size may of changed const auto count = dir_entries.size();
dir_entries.resize(count);
m_entries.reserve(count); m_entries.reserve(count);
m_entries_index.clear(); m_entries_index.clear();
@@ -1581,9 +1608,9 @@ void Menu::OnDeleteCallback() {
const auto full_path = GetNewPath(m_selected_path, entry.name); const auto full_path = GetNewPath(m_selected_path, entry.name);
if (entry.IsDir()) { if (entry.IsDir()) {
s64 count{}; bool empty{};
m_fs->DirGetEntryCount(full_path, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles | FsDirOpenMode_NoFileSize, &count); m_fs->IsDirEmpty(full_path, &empty);
if (!count) { if (empty) {
m_fs->DeleteDirectory(full_path); m_fs->DeleteDirectory(full_path);
use_progress_box = false; use_progress_box = false;
} }
@@ -1726,7 +1753,7 @@ void Menu::OnPasteCallback() {
m_fs->CreateDirectory(dst_path); m_fs->CreateDirectory(dst_path);
} else { } else {
pbox->NewTransfer("Copying "_i18n + src_path); pbox->NewTransfer("Copying "_i18n + src_path);
R_TRY(pbox->CopyFile(src_path, dst_path)); R_TRY(pbox->CopyFile(m_fs.get(), src_path, dst_path));
} }
} }
@@ -1755,7 +1782,7 @@ void Menu::OnPasteCallback() {
pbox->NewTransfer("Copying "_i18n + src_path); pbox->NewTransfer("Copying "_i18n + src_path);
log_write("copying: %s to %s\n", src_path.s, dst_path.s); log_write("copying: %s to %s\n", src_path.s, dst_path.s);
R_TRY(pbox->CopyFile(src_path, dst_path)); R_TRY(pbox->CopyFile(m_fs.get(), src_path, dst_path));
} }
} }
} }
@@ -1776,7 +1803,8 @@ void Menu::OnRenameCallback() {
} }
auto Menu::CheckIfUpdateFolder() -> Result { auto Menu::CheckIfUpdateFolder() -> Result {
R_UNLESS(m_fs_type == FsType::Sd, FsError_InvalidMountName); R_UNLESS(IsSd(), FsError_InvalidMountName);
R_UNLESS(m_fs->IsNative(), 0x1);
// check if we have already tried to find daybreak // check if we have already tried to find daybreak
if (m_daybreak_path.has_value() && m_daybreak_path.value().empty()) { if (m_daybreak_path.has_value() && m_daybreak_path.value().empty()) {
@@ -1799,12 +1827,8 @@ auto Menu::CheckIfUpdateFolder() -> Result {
log_write("found daybreak in: %s\n", m_daybreak_path.value().s); log_write("found daybreak in: %s\n", m_daybreak_path.value().s);
} }
FsDir d;
R_TRY(m_fs->OpenDirectory(m_path, FsDirOpenMode_ReadDirs, &d));
ON_SCOPE_EXIT(m_fs->DirClose(&d));
s64 count; s64 count;
R_TRY(m_fs->DirGetEntryCount(&d, &count)); R_TRY(GetNative()->DirGetEntryCount(m_path, FsDirOpenMode_ReadDirs, &count));
// check that we are at the bottom level // check that we are at the bottom level
R_UNLESS(count == 0, 0x1); R_UNLESS(count == 0, 0x1);
@@ -1826,15 +1850,10 @@ auto Menu::get_collection(const fs::FsPath& path, const fs::FsPath& parent_name,
out.parent_name = parent_name; out.parent_name = parent_name;
const auto fetch = [this, &path](std::vector<FsDirectoryEntry>& out, u32 flags) -> Result { const auto fetch = [this, &path](std::vector<FsDirectoryEntry>& out, u32 flags) -> Result {
FsDir d; fs::Dir d;
R_TRY(m_fs->OpenDirectory(path, flags, &d)); R_TRY(m_fs->OpenDirectory(path, flags, &d));
ON_SCOPE_EXIT(m_fs->DirClose(&d)); ON_SCOPE_EXIT(m_fs->DirClose(&d));
return m_fs->DirReadAll(&d, out);
s64 count;
R_TRY(m_fs->DirGetEntryCount(&d, &count));
out.resize(count);
return m_fs->DirRead(&d, &count, out.size(), out.data());
}; };
if (inc_file) { if (inc_file) {
@@ -1871,9 +1890,9 @@ auto Menu::get_collections(const fs::FsPath& path, const fs::FsPath& parent_name
R_SUCCEED(); R_SUCCEED();
} }
void Menu::SetFs(const fs::FsPath& new_path, u32 _new_type) { void Menu::SetFs(const fs::FsPath& new_path, const FsEntry& new_entry) {
const auto new_type = static_cast<FsType>(_new_type); if (m_fs && m_fs_entry.root == new_entry.root && m_fs_entry.type == new_entry.type) {
if (m_fs && new_type == m_fs_type) { log_write("same fs, ignoring\n");
return; return;
} }
@@ -1888,28 +1907,26 @@ void Menu::SetFs(const fs::FsPath& new_path, u32 _new_type) {
m_selected_path.clear(); m_selected_path.clear();
m_selected_count = 0; m_selected_count = 0;
m_selected_type = SelectedType::None; m_selected_type = SelectedType::None;
m_fs_entry = new_entry;
switch (new_type) { switch (new_entry.type) {
default: case FsType::Sd: case FsType::Sd:
m_fs = std::make_unique<fs::FsNativeSd>(m_ignore_read_only.Get()); m_fs = std::make_unique<fs::FsNativeSd>(m_ignore_read_only.Get());
m_fs_type = FsType::Sd;
log_write("doing fs: %u\n", _new_type);
break; break;
case FsType::ImageNand: case FsType::ImageNand:
m_fs = std::make_unique<fs::FsNativeImage>(FsImageDirectoryId_Nand); m_fs = std::make_unique<fs::FsNativeImage>(FsImageDirectoryId_Nand);
m_fs_type = FsType::ImageNand;
log_write("doing image nand\n");
break; break;
case FsType::ImageSd: case FsType::ImageSd:
m_fs = std::make_unique<fs::FsNativeImage>(FsImageDirectoryId_Sd); m_fs = std::make_unique<fs::FsNativeImage>(FsImageDirectoryId_Sd);
m_fs_type = FsType::ImageSd; break;
log_write("doing image sd\n"); case FsType::Stdio:
m_fs = std::make_unique<fs::FsStdio>(true, new_entry.root);
break; break;
} }
if (HasFocus()) { if (HasFocus()) {
if (m_path.empty()) { if (m_path.empty()) {
Scan("/"); Scan(m_fs->Root());
} else { } else {
Scan(m_path); Scan(m_path);
} }

View File

@@ -219,41 +219,36 @@ auto ProgressBox::ShouldExitResult() -> Result {
R_SUCCEED(); R_SUCCEED();
} }
auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result { auto ProgressBox::CopyFile(fs::Fs* fs, const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {
fs::FsNativeSd fs; fs::File src_file;
R_TRY(fs.GetFsOpenResult()); R_TRY(fs->OpenFile(src_path, FsOpenMode_Read, &src_file));
ON_SCOPE_EXIT(fs->FileClose(&src_file));
FsFile src_file;
R_TRY(fs.OpenFile(src_path, FsOpenMode_Read, &src_file));
ON_SCOPE_EXIT(fsFileClose(&src_file));
s64 src_size; s64 src_size;
R_TRY(fsFileGetSize(&src_file, &src_size)); R_TRY(fs->FileGetSize(&src_file, &src_size));
// this can fail if it already exists so we ignore the result. // this can fail if it already exists so we ignore the result.
// if the file actually failed to be created, the result is implicitly // if the file actually failed to be created, the result is implicitly
// handled when we try and open it for writing. // handled when we try and open it for writing.
fs.CreateFile(dst_path, src_size, 0); fs->CreateFile(dst_path, src_size, 0);
FsFile dst_file; fs::File dst_file;
R_TRY(fs.OpenFile(dst_path, FsOpenMode_Write, &dst_file)); R_TRY(fs->OpenFile(dst_path, FsOpenMode_Write, &dst_file));
ON_SCOPE_EXIT(fsFileClose(&dst_file)); ON_SCOPE_EXIT(fs->FileClose(&dst_file));
R_TRY(fsFileSetSize(&dst_file, src_size)); R_TRY(fs->FileSetSize(&dst_file, src_size));
s64 offset{}; s64 offset{};
std::vector<u8> buf(1024*1024*4); // 4MiB std::vector<u8> buf(1024*1024*4); // 4MiB
while (offset < src_size) { while (offset < src_size) {
if (ShouldExit()) { R_TRY(ShouldExitResult());
R_THROW(0xFFFF);
}
u64 bytes_read; u64 bytes_read;
R_TRY(fsFileRead(&src_file, offset, buf.data(), buf.size(), 0, &bytes_read)); R_TRY(fs->FileRead(&src_file, offset, buf.data(), buf.size(), 0, &bytes_read));
Yield(); Yield();
R_TRY(fsFileWrite(&dst_file, offset, buf.data(), bytes_read, FsWriteOption_None)); R_TRY(fs->FileWrite(&dst_file, offset, buf.data(), bytes_read, FsWriteOption_None));
Yield(); Yield();
UpdateTransfer(offset, src_size); UpdateTransfer(offset, src_size);
@@ -263,6 +258,12 @@ auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_pat
R_SUCCEED(); R_SUCCEED();
} }
auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {
fs::FsNativeSd fs;
R_TRY(fs.GetFsOpenResult());
return CopyFile(&fs, src_path, dst_path);
}
void ProgressBox::Yield() { void ProgressBox::Yield() {
svcSleepThread(YieldType_WithoutCoreMigration); svcSleepThread(YieldType_WithoutCoreMigration);
} }