diff --git a/sphaira/include/dumper.hpp b/sphaira/include/dumper.hpp index e0c98e3..cac1725 100644 --- a/sphaira/include/dumper.hpp +++ b/sphaira/include/dumper.hpp @@ -23,8 +23,6 @@ enum DumpLocationType { DumpLocationType_DevNull, // dump to stdio, ideal for custom mount points using devoptab, such as hdd. DumpLocationType_Stdio, - // dump to custom locations found in locations.ini. - DumpLocationType_Network, }; enum DumpLocationFlag { @@ -33,8 +31,7 @@ enum DumpLocationFlag { DumpLocationFlag_UsbS2S = 1 << DumpLocationType_UsbS2S, DumpLocationFlag_DevNull = 1 << DumpLocationType_DevNull, DumpLocationFlag_Stdio = 1 << DumpLocationType_Stdio, - DumpLocationFlag_Network = 1 << DumpLocationType_Network, - DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_Usb | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio | DumpLocationFlag_Network, + DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_Usb | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio, }; struct DumpEntry { @@ -44,7 +41,6 @@ struct DumpEntry { struct DumpLocation { DumpEntry entry{}; - location::Entries network{}; location::StdioEntries stdio{}; }; diff --git a/sphaira/include/location.hpp b/sphaira/include/location.hpp index 3411ff1..aa43dd7 100644 --- a/sphaira/include/location.hpp +++ b/sphaira/include/location.hpp @@ -11,21 +11,6 @@ namespace sphaira::location { 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; - -auto Load() -> Entries; -void Add(const Entry& e); - // helper for hdd devices. // this doesn't really belong in this header, however // locations likely will be renamed to something more generic soon. @@ -36,6 +21,8 @@ struct StdioEntry { std::string name{}; // FsEntryFlag u32 flags{}; + // optional dump path inside the mount point. + std::string dump_path{}; }; using StdioEntries = std::vector; diff --git a/sphaira/include/ui/menus/filebrowser.hpp b/sphaira/include/ui/menus/filebrowser.hpp index 2437013..e35158f 100644 --- a/sphaira/include/ui/menus/filebrowser.hpp +++ b/sphaira/include/ui/menus/filebrowser.hpp @@ -16,18 +16,16 @@ enum FsOption : u32 { // can split screen. FsOption_CanSplit = BIT(0), - // can upload files. - FsOption_CanUpload = BIT(1), // can selected multiple files. - FsOption_CanSelect = BIT(2), + FsOption_CanSelect = BIT(1), // shows the option to install. - FsOption_CanInstall = BIT(3), + FsOption_CanInstall = BIT(2), // loads file assoc. - FsOption_LoadAssoc = BIT(4), + FsOption_LoadAssoc = BIT(3), // 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_Picker = FsOption_NONE, }; diff --git a/sphaira/include/utils/devoptab_common.hpp b/sphaira/include/utils/devoptab_common.hpp index 9d503c5..8648468 100644 --- a/sphaira/include/utils/devoptab_common.hpp +++ b/sphaira/include/utils/devoptab_common.hpp @@ -128,6 +128,7 @@ struct MountConfig { std::string url{}; std::string user{}; std::string pass{}; + std::string dump_path{}; std::optional port{}; int timeout{3000}; // 3 seconds. bool read_only{}; diff --git a/sphaira/source/download.cpp b/sphaira/source/download.cpp index 9c0b133..2ef8a33 100644 --- a/sphaira/source/download.cpp +++ b/sphaira/source/download.cpp @@ -33,8 +33,6 @@ namespace { constexpr auto API_AGENT = "TotalJustice"; constexpr u64 CHUNK_SIZE = 1024*1024; constexpr auto MAX_THREADS = 4; -constexpr int THREAD_PRIO = 0x2F; -constexpr int THREAD_CORE = 2; std::atomic_bool g_running{}; CURLSH* g_curl_share{}; @@ -62,11 +60,6 @@ struct SeekCustomData { 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 { const auto key = crc32Calculate(path.s, path.size()); return std::to_string(key); @@ -595,22 +588,16 @@ auto EscapeString(CURL* curl, const std::string& str) -> std::string { return result; } -auto EncodeUrl(std::string url) -> std::string { +auto EncodeUrl(const std::string& url) -> std::string { 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(); R_UNLESS(clu, url); ON_SCOPE_EXIT(curl_url_cleanup(clu)); log_write("[CURL] setting url\n"); 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); log_write("[CURL] set url success\n"); @@ -834,13 +821,6 @@ auto UploadInternal(CURL* curl, const Api& e) -> ApiResult { 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 url = e.GetUrl() + "/" + info.m_name; 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}; } -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) { mutexLock(&g_mutex_share[data]); } diff --git a/sphaira/source/dumper.cpp b/sphaira/source/dumper.cpp index 3d26a36..d9c410d 100644 --- a/sphaira/source/dumper.cpp +++ b/sphaira/source/dumper.cpp @@ -372,7 +372,8 @@ Result DumpToFileNative(ui::ProgressBox* pbox, BaseSource* source, std::span paths, const CustomTransfer& custom_transfer) { 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) { @@ -486,54 +487,6 @@ Result DumpToDevNull(ui::ProgressBox* pbox, BaseSource* source, std::span 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 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; std::vector 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); if (location_flags & (1 << DumpLocationType_Stdio)) { 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& source, const DumpLocation& location, const std::vector& paths, const CustomTransfer& custom_transfer) { - if (location.entry.type == DumpLocationType_Network) { - R_TRY(DumpToNetwork(pbox, location.network[location.entry.index], source.get(), paths)); - } else if (location.entry.type == DumpLocationType_Stdio) { + if (location.entry.type == DumpLocationType_Stdio) { R_TRY(DumpToStdio(pbox, location.stdio[location.entry.index], source.get(), paths, custom_transfer)); } else if (location.entry.type == DumpLocationType_SdCard) { R_TRY(DumpToFileNative(pbox, source.get(), paths, custom_transfer)); diff --git a/sphaira/source/fs.cpp b/sphaira/source/fs.cpp index 394db8f..17aafbb 100644 --- a/sphaira/source/fs.cpp +++ b/sphaira/source/fs.cpp @@ -215,14 +215,18 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig 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, "/"); } + + // 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(); } diff --git a/sphaira/source/location.cpp b/sphaira/source/location.cpp index 8590a2d..d513702 100644 --- a/sphaira/source/location.cpp +++ b/sphaira/source/location.cpp @@ -12,71 +12,8 @@ namespace sphaira::location { namespace { -constexpr fs::FsPath location_path{"/config/sphaira/locations.ini"}; - } // 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(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 { StdioEntries out{}; diff --git a/sphaira/source/ui/menus/filebrowser.cpp b/sphaira/source/ui/menus/filebrowser.cpp index 372b2ea..446416d 100644 --- a/sphaira/source/ui/menus/filebrowser.cpp +++ b/sphaira/source/ui/menus/filebrowser.cpp @@ -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( - "Select upload location"_i18n, items, [this, network_locations](auto op_index){ - if (!op_index) { - return; - } - - const auto loc = network_locations[*op_index]; - App::Push(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 { App::SetBoostMode(true); 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("Upload"_i18n, [this](){ - UploadFiles(); - }); - } - if (m_entries_current.size() && !m_selected_count && IsExtension(GetEntry().GetExtension(), THEME_MUSIC_EXTENSIONS)) { options->Add("Set as background music"_i18n, [this](){ const auto rc = App::SetDefaultBackgroundMusic(GetFs(), GetNewPathCurrent()); diff --git a/sphaira/source/utils/devoptab_common.cpp b/sphaira/source/utils/devoptab_common.cpp index e2077dc..d2e5028 100644 --- a/sphaira/source/utils/devoptab_common.cpp +++ b/sphaira/source/utils/devoptab_common.cpp @@ -792,6 +792,8 @@ Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file e->back().user = Value; } else if (!std::strcmp(Key, "pass")) { e->back().pass = Value; + } else if (!std::strcmp(Key, "dump_path")) { + e->back().dump_path = Value; } else if (!std::strcmp(Key, "port")) { const auto port = ini_parse_getl(Value, -1); if (port < 0 || port > 65535) { @@ -1379,7 +1381,7 @@ Result GetNetworkDevices(location::StdioEntries& out) { 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); } } diff --git a/sphaira/source/utils/devoptab_http.cpp b/sphaira/source/utils/devoptab_http.cpp index dc180f8..82d2f5e 100644 --- a/sphaira/source/utils/devoptab_http.cpp +++ b/sphaira/source/utils/devoptab_http.cpp @@ -181,7 +181,7 @@ bool Device::http_stat(const std::string& path, struct stat* st, bool is_dir) { // handle error codes. 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; }