devoptab: deprecate locations.ini in favour of hdd/network mounts, better handle folder creation errors.
This commit is contained in:
@@ -23,8 +23,6 @@ enum DumpLocationType {
|
|||||||
DumpLocationType_DevNull,
|
DumpLocationType_DevNull,
|
||||||
// dump to stdio, ideal for custom mount points using devoptab, such as hdd.
|
// dump to stdio, ideal for custom mount points using devoptab, such as hdd.
|
||||||
DumpLocationType_Stdio,
|
DumpLocationType_Stdio,
|
||||||
// dump to custom locations found in locations.ini.
|
|
||||||
DumpLocationType_Network,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DumpLocationFlag {
|
enum DumpLocationFlag {
|
||||||
@@ -33,8 +31,7 @@ enum DumpLocationFlag {
|
|||||||
DumpLocationFlag_UsbS2S = 1 << DumpLocationType_UsbS2S,
|
DumpLocationFlag_UsbS2S = 1 << DumpLocationType_UsbS2S,
|
||||||
DumpLocationFlag_DevNull = 1 << DumpLocationType_DevNull,
|
DumpLocationFlag_DevNull = 1 << DumpLocationType_DevNull,
|
||||||
DumpLocationFlag_Stdio = 1 << DumpLocationType_Stdio,
|
DumpLocationFlag_Stdio = 1 << DumpLocationType_Stdio,
|
||||||
DumpLocationFlag_Network = 1 << DumpLocationType_Network,
|
DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_Usb | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio,
|
||||||
DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_Usb | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio | DumpLocationFlag_Network,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DumpEntry {
|
struct DumpEntry {
|
||||||
@@ -44,7 +41,6 @@ struct DumpEntry {
|
|||||||
|
|
||||||
struct DumpLocation {
|
struct DumpLocation {
|
||||||
DumpEntry entry{};
|
DumpEntry entry{};
|
||||||
location::Entries network{};
|
|
||||||
location::StdioEntries stdio{};
|
location::StdioEntries stdio{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,21 +11,6 @@ namespace sphaira::location {
|
|||||||
|
|
||||||
using FsEntryFlag = ui::menu::filebrowser::FsEntryFlag;
|
using FsEntryFlag = ui::menu::filebrowser::FsEntryFlag;
|
||||||
|
|
||||||
struct Entry {
|
|
||||||
std::string name{};
|
|
||||||
std::string url{};
|
|
||||||
std::string user{};
|
|
||||||
std::string pass{};
|
|
||||||
std::string bearer{};
|
|
||||||
std::string pub_key{};
|
|
||||||
std::string priv_key{};
|
|
||||||
u16 port{};
|
|
||||||
};
|
|
||||||
using Entries = std::vector<Entry>;
|
|
||||||
|
|
||||||
auto Load() -> Entries;
|
|
||||||
void Add(const Entry& e);
|
|
||||||
|
|
||||||
// helper for hdd devices.
|
// helper for hdd devices.
|
||||||
// this doesn't really belong in this header, however
|
// this doesn't really belong in this header, however
|
||||||
// locations likely will be renamed to something more generic soon.
|
// locations likely will be renamed to something more generic soon.
|
||||||
@@ -36,6 +21,8 @@ struct StdioEntry {
|
|||||||
std::string name{};
|
std::string name{};
|
||||||
// FsEntryFlag
|
// FsEntryFlag
|
||||||
u32 flags{};
|
u32 flags{};
|
||||||
|
// optional dump path inside the mount point.
|
||||||
|
std::string dump_path{};
|
||||||
};
|
};
|
||||||
|
|
||||||
using StdioEntries = std::vector<StdioEntry>;
|
using StdioEntries = std::vector<StdioEntry>;
|
||||||
|
|||||||
@@ -16,18 +16,16 @@ enum FsOption : u32 {
|
|||||||
|
|
||||||
// can split screen.
|
// can split screen.
|
||||||
FsOption_CanSplit = BIT(0),
|
FsOption_CanSplit = BIT(0),
|
||||||
// can upload files.
|
|
||||||
FsOption_CanUpload = BIT(1),
|
|
||||||
// can selected multiple files.
|
// can selected multiple files.
|
||||||
FsOption_CanSelect = BIT(2),
|
FsOption_CanSelect = BIT(1),
|
||||||
// shows the option to install.
|
// shows the option to install.
|
||||||
FsOption_CanInstall = BIT(3),
|
FsOption_CanInstall = BIT(2),
|
||||||
// loads file assoc.
|
// loads file assoc.
|
||||||
FsOption_LoadAssoc = BIT(4),
|
FsOption_LoadAssoc = BIT(3),
|
||||||
// do not prompt on exit even if not tabbed.
|
// do not prompt on exit even if not tabbed.
|
||||||
FsOption_DoNotPrompt = BIT(5),
|
FsOption_DoNotPrompt = BIT(4),
|
||||||
|
|
||||||
FsOption_Normal = FsOption_LoadAssoc | FsOption_CanInstall | FsOption_CanSplit | FsOption_CanUpload | FsOption_CanSelect,
|
FsOption_Normal = FsOption_LoadAssoc | FsOption_CanInstall | FsOption_CanSplit | FsOption_CanSelect,
|
||||||
FsOption_All = FsOption_DoNotPrompt | FsOption_Normal,
|
FsOption_All = FsOption_DoNotPrompt | FsOption_Normal,
|
||||||
FsOption_Picker = FsOption_NONE,
|
FsOption_Picker = FsOption_NONE,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ struct MountConfig {
|
|||||||
std::string url{};
|
std::string url{};
|
||||||
std::string user{};
|
std::string user{};
|
||||||
std::string pass{};
|
std::string pass{};
|
||||||
|
std::string dump_path{};
|
||||||
std::optional<long> port{};
|
std::optional<long> port{};
|
||||||
int timeout{3000}; // 3 seconds.
|
int timeout{3000}; // 3 seconds.
|
||||||
bool read_only{};
|
bool read_only{};
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ namespace {
|
|||||||
constexpr auto API_AGENT = "TotalJustice";
|
constexpr auto API_AGENT = "TotalJustice";
|
||||||
constexpr u64 CHUNK_SIZE = 1024*1024;
|
constexpr u64 CHUNK_SIZE = 1024*1024;
|
||||||
constexpr auto MAX_THREADS = 4;
|
constexpr auto MAX_THREADS = 4;
|
||||||
constexpr int THREAD_PRIO = 0x2F;
|
|
||||||
constexpr int THREAD_CORE = 2;
|
|
||||||
|
|
||||||
std::atomic_bool g_running{};
|
std::atomic_bool g_running{};
|
||||||
CURLSH* g_curl_share{};
|
CURLSH* g_curl_share{};
|
||||||
@@ -62,11 +60,6 @@ struct SeekCustomData {
|
|||||||
s64 size{};
|
s64 size{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// helper for creating webdav folders as libcurl does not have built-in
|
|
||||||
// support for it.
|
|
||||||
// only creates the folders if they don't exist.
|
|
||||||
auto WebdavCreateFolder(CURL* curl, const Api& e) -> bool;
|
|
||||||
|
|
||||||
auto generate_key_from_path(const fs::FsPath& path) -> std::string {
|
auto generate_key_from_path(const fs::FsPath& path) -> std::string {
|
||||||
const auto key = crc32Calculate(path.s, path.size());
|
const auto key = crc32Calculate(path.s, path.size());
|
||||||
return std::to_string(key);
|
return std::to_string(key);
|
||||||
@@ -595,22 +588,16 @@ auto EscapeString(CURL* curl, const std::string& str) -> std::string {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto EncodeUrl(std::string url) -> std::string {
|
auto EncodeUrl(const std::string& url) -> std::string {
|
||||||
log_write("[CURL] encoding url\n");
|
log_write("[CURL] encoding url\n");
|
||||||
|
|
||||||
if (url.starts_with("webdav://")) {
|
|
||||||
log_write("[CURL] updating host\n");
|
|
||||||
url.replace(0, std::strlen("webdav"), "https");
|
|
||||||
log_write("[CURL] updated host: %s\n", url.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto clu = curl_url();
|
auto clu = curl_url();
|
||||||
R_UNLESS(clu, url);
|
R_UNLESS(clu, url);
|
||||||
ON_SCOPE_EXIT(curl_url_cleanup(clu));
|
ON_SCOPE_EXIT(curl_url_cleanup(clu));
|
||||||
|
|
||||||
log_write("[CURL] setting url\n");
|
log_write("[CURL] setting url\n");
|
||||||
CURLUcode clu_code;
|
CURLUcode clu_code;
|
||||||
clu_code = curl_url_set(clu, CURLUPART_URL, url.c_str(), CURLU_URLENCODE);
|
clu_code = curl_url_set(clu, CURLUPART_URL, url.c_str(), CURLU_DEFAULT_SCHEME | CURLU_URLENCODE);
|
||||||
R_UNLESS(clu_code == CURLUE_OK, url);
|
R_UNLESS(clu_code == CURLUE_OK, url);
|
||||||
log_write("[CURL] set url success\n");
|
log_write("[CURL] set url success\n");
|
||||||
|
|
||||||
@@ -834,13 +821,6 @@ auto UploadInternal(CURL* curl, const Api& e) -> ApiResult {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.GetUrl().starts_with("webdav://")) {
|
|
||||||
if (!WebdavCreateFolder(curl, e)) {
|
|
||||||
log_write("[CURL] failed to create webdav folder, aborting\n");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& info = e.GetUploadInfo();
|
const auto& info = e.GetUploadInfo();
|
||||||
const auto url = e.GetUrl() + "/" + info.m_name;
|
const auto url = e.GetUrl() + "/" + info.m_name;
|
||||||
const auto encoded_url = EncodeUrl(url);
|
const auto encoded_url = EncodeUrl(url);
|
||||||
@@ -960,76 +940,6 @@ auto UploadInternal(CURL* curl, const Api& e) -> ApiResult {
|
|||||||
return {success, http_code, header_out, chunk_out.data};
|
return {success, http_code, header_out, chunk_out.data};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto WebdavCreateFolder(CURL* curl, const Api& e) -> bool {
|
|
||||||
// if using webdav, extract the file path and create the directories.
|
|
||||||
// https://github.com/WebDAVDevs/webdav-request-samples/blob/master/webdav_curl.md
|
|
||||||
if (e.GetUrl().starts_with("webdav://")) {
|
|
||||||
log_write("[CURL] found webdav url\n");
|
|
||||||
|
|
||||||
const auto info = e.GetUploadInfo();
|
|
||||||
if (info.m_name.empty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& file_path = info.m_name;
|
|
||||||
log_write("got file path: %s\n", file_path.c_str());
|
|
||||||
|
|
||||||
const auto file_loc = file_path.find_last_of('/');
|
|
||||||
if (file_loc == file_path.npos) {
|
|
||||||
log_write("failed to find last slash\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto path_view = file_path.substr(0, file_loc);
|
|
||||||
log_write("got folder path: %s\n", path_view.c_str());
|
|
||||||
|
|
||||||
auto e2 = e;
|
|
||||||
e2.SetOption(Path{});
|
|
||||||
e2.SetOption(Url{e.GetUrl() + "/" + path_view});
|
|
||||||
e2.SetOption(Flags{e.GetFlags() | Flag_NoBody});
|
|
||||||
e2.SetOption(CustomRequest{"PROPFIND"});
|
|
||||||
e2.SetOption(Header{
|
|
||||||
{ "Depth", "0" },
|
|
||||||
});
|
|
||||||
|
|
||||||
// test to see if the directory exists first.
|
|
||||||
const auto exist_result = DownloadInternal(curl, e2);
|
|
||||||
if (exist_result.success) {
|
|
||||||
log_write("[CURL] folder already exist: %s\n", path_view.c_str());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
log_write("[CURL] folder does NOT exist, manually creating: %s\n", path_view.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// make the request to create the folder.
|
|
||||||
std::string folder;
|
|
||||||
for (const auto dir : std::views::split(path_view, '/')) {
|
|
||||||
if (dir.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
folder += "/" + std::string{dir.data(), dir.size()};
|
|
||||||
e2.SetOption(Url{e.GetUrl() + folder});
|
|
||||||
e2.SetOption(Header{});
|
|
||||||
e2.SetOption(CustomRequest{"MKCOL"});
|
|
||||||
|
|
||||||
const auto result = DownloadInternal(curl, e2);
|
|
||||||
if (result.code == 201) {
|
|
||||||
log_write("[CURL] created webdav directory\n");
|
|
||||||
} else if (result.code == 405) {
|
|
||||||
log_write("[CURL] webdav directory already exists: %ld\n", result.code);
|
|
||||||
} else {
|
|
||||||
log_write("[CURL] failed to create webdav directory: %ld\n", result.code);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log_write("[CURL] not a webdav url: %s\n", e.GetUrl().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void my_lock(CURL *handle, curl_lock_data data, curl_lock_access laccess, void *useptr) {
|
void my_lock(CURL *handle, curl_lock_data data, curl_lock_access laccess, void *useptr) {
|
||||||
mutexLock(&g_mutex_share[data]);
|
mutexLock(&g_mutex_share[data]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -372,7 +372,8 @@ Result DumpToFileNative(ui::ProgressBox* pbox, BaseSource* source, std::span<con
|
|||||||
|
|
||||||
Result DumpToStdio(ui::ProgressBox* pbox, const location::StdioEntry& loc, BaseSource* source, std::span<const fs::FsPath> paths, const CustomTransfer& custom_transfer) {
|
Result DumpToStdio(ui::ProgressBox* pbox, const location::StdioEntry& loc, BaseSource* source, std::span<const fs::FsPath> paths, const CustomTransfer& custom_transfer) {
|
||||||
fs::FsStdio fs{};
|
fs::FsStdio fs{};
|
||||||
return DumpToFile(pbox, &fs, loc.mount, source, paths, custom_transfer);
|
const auto mount_path = fs::AppendPath(loc.mount, loc.dump_path);
|
||||||
|
return DumpToFile(pbox, &fs, mount_path, source, paths, custom_transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result DumpToUsbS2SInternal(ui::ProgressBox* pbox, UsbTest* usb) {
|
Result DumpToUsbS2SInternal(ui::ProgressBox* pbox, UsbTest* usb) {
|
||||||
@@ -486,54 +487,6 @@ Result DumpToDevNull(ui::ProgressBox* pbox, BaseSource* source, std::span<const
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result DumpToNetwork(ui::ProgressBox* pbox, const location::Entry& loc, BaseSource* source, std::span<const fs::FsPath> paths) {
|
|
||||||
for (auto path : paths) {
|
|
||||||
R_TRY(pbox->ShouldExitResult());
|
|
||||||
|
|
||||||
const auto file_size = source->GetSize(path);
|
|
||||||
pbox->SetImage(source->GetIcon(path));
|
|
||||||
pbox->SetTitle(source->GetName(path));
|
|
||||||
pbox->NewTransfer(path);
|
|
||||||
|
|
||||||
R_TRY(thread::TransferPull(pbox, file_size,
|
|
||||||
[&](void* data, s64 off, s64 size, u64* bytes_read) -> Result {
|
|
||||||
return source->Read(path, data, off, size, bytes_read);
|
|
||||||
},
|
|
||||||
[&](thread::PullCallback pull) -> Result {
|
|
||||||
s64 offset{};
|
|
||||||
const auto result = curl::Api().FromMemory(
|
|
||||||
CURL_LOCATION_TO_API(loc),
|
|
||||||
curl::OnProgress{pbox->OnDownloadProgressCallback()},
|
|
||||||
curl::UploadInfo{
|
|
||||||
path, file_size,
|
|
||||||
[&](void *ptr, size_t size) -> size_t {
|
|
||||||
// curl will request past the size of the file, causing an error.
|
|
||||||
if (offset >= file_size) {
|
|
||||||
log_write("finished file upload\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 bytes_read{};
|
|
||||||
if (R_FAILED(pull(ptr, size, &bytes_read))) {
|
|
||||||
log_write("failed to read in custom callback: %zd size: %zd\n", offset, size);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += bytes_read;
|
|
||||||
return bytes_read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
R_UNLESS(result.success, Result_DumpFailedNetworkUpload);
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void DumpGetLocation(const std::string& title, u32 location_flags, const OnLocation& on_loc, const CustomTransfer& custom_transfer) {
|
void DumpGetLocation(const std::string& title, u32 location_flags, const OnLocation& on_loc, const CustomTransfer& custom_transfer) {
|
||||||
@@ -541,14 +494,6 @@ void DumpGetLocation(const std::string& title, u32 location_flags, const OnLocat
|
|||||||
ui::PopupList::Items items;
|
ui::PopupList::Items items;
|
||||||
std::vector<DumpEntry> dump_entries;
|
std::vector<DumpEntry> dump_entries;
|
||||||
|
|
||||||
out.network = location::Load();
|
|
||||||
if (!custom_transfer && location_flags & (1 << DumpLocationType_Network)) {
|
|
||||||
for (s32 i = 0; i < std::size(out.network); i++) {
|
|
||||||
dump_entries.emplace_back(DumpLocationType_Network, i);
|
|
||||||
items.emplace_back(out.network[i].name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.stdio = location::GetStdio(true);
|
out.stdio = location::GetStdio(true);
|
||||||
if (location_flags & (1 << DumpLocationType_Stdio)) {
|
if (location_flags & (1 << DumpLocationType_Stdio)) {
|
||||||
for (s32 i = 0; i < std::size(out.stdio); i++) {
|
for (s32 i = 0; i < std::size(out.stdio); i++) {
|
||||||
@@ -578,9 +523,7 @@ void DumpGetLocation(const std::string& title, u32 location_flags, const OnLocat
|
|||||||
}
|
}
|
||||||
|
|
||||||
Result Dump(ui::ProgressBox* pbox, const std::shared_ptr<BaseSource>& source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, const CustomTransfer& custom_transfer) {
|
Result Dump(ui::ProgressBox* pbox, const std::shared_ptr<BaseSource>& source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, const CustomTransfer& custom_transfer) {
|
||||||
if (location.entry.type == DumpLocationType_Network) {
|
if (location.entry.type == DumpLocationType_Stdio) {
|
||||||
R_TRY(DumpToNetwork(pbox, location.network[location.entry.index], source.get(), paths));
|
|
||||||
} else if (location.entry.type == DumpLocationType_Stdio) {
|
|
||||||
R_TRY(DumpToStdio(pbox, location.stdio[location.entry.index], source.get(), paths, custom_transfer));
|
R_TRY(DumpToStdio(pbox, location.stdio[location.entry.index], source.get(), paths, custom_transfer));
|
||||||
} else if (location.entry.type == DumpLocationType_SdCard) {
|
} else if (location.entry.type == DumpLocationType_SdCard) {
|
||||||
R_TRY(DumpToFileNative(pbox, source.get(), paths, custom_transfer));
|
R_TRY(DumpToFileNative(pbox, source.get(), paths, custom_transfer));
|
||||||
|
|||||||
@@ -215,14 +215,18 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig
|
|||||||
rc = CreateDirectory(path, ignore_read_only);
|
rc = CreateDirectory(path, ignore_read_only);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
|
||||||
log_write("failed to create folder: %s\n", path.s);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
// log_write("created_directory: %s\n", path);
|
|
||||||
std::strcat(path, "/");
|
std::strcat(path, "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only check if the last folder creation failed.
|
||||||
|
// reason being is that it may try to create "/" root folder, which some network
|
||||||
|
// fs will return a EPERM/EACCES error.
|
||||||
|
// however if the last directory failed, then it is a real error.
|
||||||
|
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||||
|
log_write("failed to create folder: %s\n", path.s);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,71 +12,8 @@
|
|||||||
namespace sphaira::location {
|
namespace sphaira::location {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr fs::FsPath location_path{"/config/sphaira/locations.ini"};
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void Add(const Entry& e) {
|
|
||||||
if (e.name.empty() || e.url.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ini_puts(e.name.c_str(), "url", e.url.c_str(), location_path);
|
|
||||||
if (!e.user.empty()) {
|
|
||||||
ini_puts(e.name.c_str(), "user", e.user.c_str(), location_path);
|
|
||||||
}
|
|
||||||
if (!e.pass.empty()) {
|
|
||||||
ini_puts(e.name.c_str(), "pass", e.pass.c_str(), location_path);
|
|
||||||
}
|
|
||||||
if (!e.bearer.empty()) {
|
|
||||||
ini_puts(e.name.c_str(), "bearer", e.bearer.c_str(), location_path);
|
|
||||||
}
|
|
||||||
if (!e.pub_key.empty()) {
|
|
||||||
ini_puts(e.name.c_str(), "pub_key", e.pub_key.c_str(), location_path);
|
|
||||||
}
|
|
||||||
if (!e.priv_key.empty()) {
|
|
||||||
ini_puts(e.name.c_str(), "priv_key", e.priv_key.c_str(), location_path);
|
|
||||||
}
|
|
||||||
if (e.port) {
|
|
||||||
ini_putl(e.name.c_str(), "port", e.port, location_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Load() -> Entries {
|
|
||||||
Entries out{};
|
|
||||||
|
|
||||||
auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
|
||||||
auto e = static_cast<Entries*>(UserData);
|
|
||||||
|
|
||||||
// add new entry if use section changed.
|
|
||||||
if (e->empty() || std::strcmp(Section, e->back().name.c_str())) {
|
|
||||||
e->emplace_back(Section);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!std::strcmp(Key, "url")) {
|
|
||||||
e->back().url = Value;
|
|
||||||
} else if (!std::strcmp(Key, "user")) {
|
|
||||||
e->back().user = Value;
|
|
||||||
} else if (!std::strcmp(Key, "pass")) {
|
|
||||||
e->back().pass = Value;
|
|
||||||
} else if (!std::strcmp(Key, "bearer")) {
|
|
||||||
e->back().bearer = Value;
|
|
||||||
} else if (!std::strcmp(Key, "pub_key")) {
|
|
||||||
e->back().pub_key = Value;
|
|
||||||
} else if (!std::strcmp(Key, "priv_key")) {
|
|
||||||
e->back().priv_key = Value;
|
|
||||||
} else if (!std::strcmp(Key, "port")) {
|
|
||||||
e->back().port = std::atoi(Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
ini_browse(cb, &out, location_path);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto GetStdio(bool write) -> StdioEntries {
|
auto GetStdio(bool write) -> StdioEntries {
|
||||||
StdioEntries out{};
|
StdioEntries out{};
|
||||||
|
|
||||||
|
|||||||
@@ -1005,114 +1005,6 @@ void FsView::ZipFiles(fs::FsPath zip_out) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FsView::UploadFiles() {
|
|
||||||
const auto targets = GetSelectedEntries();
|
|
||||||
|
|
||||||
const auto network_locations = location::Load();
|
|
||||||
if (network_locations.empty()) {
|
|
||||||
App::Notify("No upload locations set!"_i18n);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PopupList::Items items;
|
|
||||||
for (const auto&p : network_locations) {
|
|
||||||
items.emplace_back(p.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
App::Push<PopupList>(
|
|
||||||
"Select upload location"_i18n, items, [this, network_locations](auto op_index){
|
|
||||||
if (!op_index) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto loc = network_locations[*op_index];
|
|
||||||
App::Push<ProgressBox>(0, "Uploading"_i18n, "", [this, loc](auto pbox) -> Result {
|
|
||||||
auto targets = GetSelectedEntries();
|
|
||||||
const auto is_file_based_emummc = App::IsFileBaseEmummc();
|
|
||||||
|
|
||||||
const auto file_add = [&](s64 file_size, const fs::FsPath& file_path, const char* name) -> Result {
|
|
||||||
// the file name needs to be relative to the current directory.
|
|
||||||
const auto relative_file_name = file_path.s + std::strlen(m_path);
|
|
||||||
pbox->SetTitle(name);
|
|
||||||
pbox->NewTransfer(relative_file_name);
|
|
||||||
|
|
||||||
fs::File f;
|
|
||||||
R_TRY(m_fs->OpenFile(file_path, FsOpenMode_Read, &f));
|
|
||||||
|
|
||||||
return thread::TransferPull(pbox, file_size,
|
|
||||||
[&](void* data, s64 off, s64 size, u64* bytes_read) -> Result {
|
|
||||||
const auto rc = f.Read(off, data, size, FsReadOption_None, bytes_read);
|
|
||||||
if (m_fs->IsNative() && is_file_based_emummc) {
|
|
||||||
svcSleepThread(2e+6); // 2ms
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
},
|
|
||||||
[&](thread::PullCallback pull) -> Result {
|
|
||||||
s64 offset{};
|
|
||||||
const auto result = curl::Api().FromMemory(
|
|
||||||
CURL_LOCATION_TO_API(loc),
|
|
||||||
curl::OnProgress{pbox->OnDownloadProgressCallback()},
|
|
||||||
curl::UploadInfo{
|
|
||||||
relative_file_name, file_size,
|
|
||||||
[&](void *ptr, size_t size) -> size_t {
|
|
||||||
// curl will request past the size of the file, causing an error.
|
|
||||||
if (offset >= file_size) {
|
|
||||||
log_write("finished file upload\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 bytes_read{};
|
|
||||||
if (R_FAILED(pull(ptr, size, &bytes_read))) {
|
|
||||||
log_write("failed to read in custom callback: %zd size: %zd\n", offset, size);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += bytes_read;
|
|
||||||
return bytes_read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
R_UNLESS(result.success, Result_FileBrowserFailedUpload);
|
|
||||||
R_SUCCEED();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (auto& e : targets) {
|
|
||||||
if (e.IsFile()) {
|
|
||||||
const auto file_path = GetNewPath(e);
|
|
||||||
R_TRY(file_add(e.file_size, file_path, e.GetName().c_str()));
|
|
||||||
} else {
|
|
||||||
FsDirCollections collections;
|
|
||||||
get_collections(GetNewPath(e), e.name, collections, true);
|
|
||||||
|
|
||||||
for (const auto& collection : collections) {
|
|
||||||
for (const auto& file : collection.files) {
|
|
||||||
const auto file_path = fs::AppendPath(collection.path, file.name);
|
|
||||||
R_TRY(file_add(file.file_size, file_path, file.name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
R_SUCCEED();
|
|
||||||
}, [this](Result rc){
|
|
||||||
App::PushErrorBox(rc, "Failed to, TODO: add message here"_i18n);
|
|
||||||
m_menu->ResetSelection();
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
App::Notify("Upload successfull!"_i18n);
|
|
||||||
log_write("Upload successfull!!!\n");
|
|
||||||
} else {
|
|
||||||
App::Notify("Upload failed!"_i18n);
|
|
||||||
log_write("Upload failed!!!\n");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto FsView::Scan(fs::FsPath new_path, bool is_walk_up) -> Result {
|
auto FsView::Scan(fs::FsPath new_path, bool is_walk_up) -> Result {
|
||||||
App::SetBoostMode(true);
|
App::SetBoostMode(true);
|
||||||
ON_SCOPE_EXIT(App::SetBoostMode(false));
|
ON_SCOPE_EXIT(App::SetBoostMode(false));
|
||||||
@@ -1978,12 +1870,6 @@ void FsView::DisplayAdvancedOptions() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_entries_current.size() && (m_menu->m_options & FsOption_CanUpload)) {
|
|
||||||
options->Add<SidebarEntryCallback>("Upload"_i18n, [this](){
|
|
||||||
UploadFiles();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_entries_current.size() && !m_selected_count && IsExtension(GetEntry().GetExtension(), THEME_MUSIC_EXTENSIONS)) {
|
if (m_entries_current.size() && !m_selected_count && IsExtension(GetEntry().GetExtension(), THEME_MUSIC_EXTENSIONS)) {
|
||||||
options->Add<SidebarEntryCallback>("Set as background music"_i18n, [this](){
|
options->Add<SidebarEntryCallback>("Set as background music"_i18n, [this](){
|
||||||
const auto rc = App::SetDefaultBackgroundMusic(GetFs(), GetNewPathCurrent());
|
const auto rc = App::SetDefaultBackgroundMusic(GetFs(), GetNewPathCurrent());
|
||||||
|
|||||||
@@ -792,6 +792,8 @@ Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file
|
|||||||
e->back().user = Value;
|
e->back().user = Value;
|
||||||
} else if (!std::strcmp(Key, "pass")) {
|
} else if (!std::strcmp(Key, "pass")) {
|
||||||
e->back().pass = Value;
|
e->back().pass = Value;
|
||||||
|
} else if (!std::strcmp(Key, "dump_path")) {
|
||||||
|
e->back().dump_path = Value;
|
||||||
} else if (!std::strcmp(Key, "port")) {
|
} else if (!std::strcmp(Key, "port")) {
|
||||||
const auto port = ini_parse_getl(Value, -1);
|
const auto port = ini_parse_getl(Value, -1);
|
||||||
if (port < 0 || port > 65535) {
|
if (port < 0 || port > 65535) {
|
||||||
@@ -1379,7 +1381,7 @@ Result GetNetworkDevices(location::StdioEntries& out) {
|
|||||||
flags |= location::FsEntryFlag::FsEntryFlag_NoStatDir;
|
flags |= location::FsEntryFlag::FsEntryFlag_NoStatDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.emplace_back(entry->mount, entry->name, flags);
|
out.emplace_back(entry->mount, entry->name, flags, entry->device.config.dump_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ bool Device::http_stat(const std::string& path, struct stat* st, bool is_dir) {
|
|||||||
|
|
||||||
// handle error codes.
|
// handle error codes.
|
||||||
if (response_code != 200 && response_code != 206) {
|
if (response_code != 200 && response_code != 206) {
|
||||||
log_write("[WEBDAV] Unexpected HTTP response code: %ld\n", response_code);
|
log_write("[HTTP] Unexpected HTTP response code: %ld\n", response_code);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user