add support for mounting stdio (hdd) in the file browser.
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{};
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,12 +690,15 @@ 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) {
|
||||||
MenuBase::Update(controller, touch);
|
MenuBase::Update(controller, 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user