diff --git a/sphaira/include/download.hpp b/sphaira/include/download.hpp index 2138940..f970ede 100644 --- a/sphaira/include/download.hpp +++ b/sphaira/include/download.hpp @@ -79,6 +79,7 @@ struct UserPass { struct UploadInfo { UploadInfo() = default; + UploadInfo(const std::string& name) : m_name{name} {} UploadInfo(const std::string& name, s64 size, OnUploadCallback cb) : m_name{name}, m_size{size}, m_callback{cb} {} UploadInfo(const std::string& name, const std::vector& data) : m_name{name}, m_data{data} {} std::string m_name{}; @@ -119,6 +120,15 @@ struct DownloadEventData { StopToken stoken; }; +// helper that generates the api using an location. +#define CURL_LOCATION_TO_API(loc) \ + curl::Url{loc.url}, \ + curl::UserPass{loc.user, loc.pass}, \ + curl::Bearer{loc.bearer}, \ + curl::PubKey{loc.pub_key}, \ + curl::PrivKey{loc.priv_key}, \ + curl::Port(loc.port) + auto Init() -> bool; void Exit(); @@ -213,6 +223,7 @@ struct Api { auto FromFile(Ts&&... ts) { static_assert(std::disjunction_v...>, "Url must be specified"); static_assert(std::disjunction_v...>, "Path must be specified"); + static_assert(std::disjunction_v...>, "UploadInfo must be specified"); static_assert(!std::disjunction_v...>, "OnComplete must not be specified"); Api::set_option(std::forward(ts)...); return curl::FromFile(*this); @@ -253,6 +264,7 @@ struct Api { auto FromFileAsync(Ts&&... ts) { static_assert(std::disjunction_v...>, "Url must be specified"); static_assert(std::disjunction_v...>, "Path must be specified"); + static_assert(std::disjunction_v...>, "UploadInfo must be specified"); static_assert(std::disjunction_v...>, "OnComplete must be specified"); static_assert(std::disjunction_v...>, "StopToken must be specified"); Api::set_option(std::forward(ts)...); diff --git a/sphaira/include/ui/menus/filebrowser.hpp b/sphaira/include/ui/menus/filebrowser.hpp index 5de558b..5c2c683 100644 --- a/sphaira/include/ui/menus/filebrowser.hpp +++ b/sphaira/include/ui/menus/filebrowser.hpp @@ -136,14 +136,11 @@ struct Menu final : MenuBase { private: void SetIndex(s64 index); void InstallForwarder(); - void InstallFile(const FileEntry& target); - void InstallFiles(const std::vector& targets); - void UnzipFile(const fs::FsPath& folder, const FileEntry& target); - void UnzipFiles(fs::FsPath folder, const std::vector& targets); - - void ZipFile(const fs::FsPath& zip_path, const FileEntry& target); - void ZipFiles(fs::FsPath zip_path, const std::vector& targets); + void InstallFiles(); + void UnzipFiles(fs::FsPath folder); + void ZipFiles(fs::FsPath zip_path); + void UploadFiles(); auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result; @@ -164,15 +161,15 @@ private: } auto GetSelectedEntries() const -> std::vector { - if (!m_selected_count) { - return {}; - } - std::vector out; - for (auto&e : m_entries) { - if (e.IsSelected()) { - out.emplace_back(e); + if (!m_selected_count) { + out.emplace_back(GetEntry()); + } else { + for (auto&e : m_entries) { + if (e.IsSelected()) { + out.emplace_back(e); + } } } @@ -191,13 +188,6 @@ private: m_selected_path = m_path; } - void AddCurrentFileToSelection(SelectedType type) { - m_selected_files.emplace_back(GetEntry()); - m_selected_count++; - m_selected_type = type; - m_selected_path = m_path; - } - void ResetSelection() { m_selected_files.clear(); m_selected_count = 0; diff --git a/sphaira/source/download.cpp b/sphaira/source/download.cpp index 56cb273..f30f3f1 100644 --- a/sphaira/source/download.cpp +++ b/sphaira/source/download.cpp @@ -33,6 +33,9 @@ constexpr int THREAD_CORE = 1; std::atomic_bool g_running{}; CURLSH* g_curl_share{}; +// this is used for single threaded blocking installs. +// avoids the needed for re-creating the handle each time. +CURL* g_curl_single{}; Mutex g_mutex_share[CURL_LOCK_DATA_LAST]{}; struct UploadStruct { @@ -690,8 +693,8 @@ auto UploadInternal(CURL* curl, const Api& e) -> ApiResult { return {}; } - auto url = e.GetUrl(); const auto& info = e.GetUploadInfo(); + const auto url = e.GetUrl() + "/" + info.m_name; const bool has_file = !e.GetPath().empty() && e.GetPath() != ""; UploadStruct chunk{}; @@ -717,8 +720,6 @@ auto UploadInternal(CURL* curl, const Api& e) -> ApiResult { upload_size = info.m_data.size(); chunk.data = info.m_data; } - - url += "/" + info.m_name; } if (url.starts_with("file://")) { @@ -807,26 +808,6 @@ auto UploadInternal(CURL* curl, const Api& e) -> ApiResult { return {success, http_code, header_out, chunk_out.data}; } -auto DownloadInternal(const Api& e) -> ApiResult { - auto curl = curl_easy_init(); - if (!curl) { - log_write("curl init failed\n"); - return {}; - } - ON_SCOPE_EXIT(curl_easy_cleanup(curl)); - return DownloadInternal(curl, e); -} - -auto UploadInternal(const Api& e) -> ApiResult { - auto curl = curl_easy_init(); - if (!curl) { - log_write("curl init failed\n"); - return {}; - } - ON_SCOPE_EXIT(curl_easy_cleanup(curl)); - return UploadInternal(curl, e); -} - void my_lock(CURL *handle, curl_lock_data data, curl_lock_access laccess, void *useptr) { mutexLock(&g_mutex_share[data]); } @@ -848,12 +829,6 @@ void ThreadEntry::ThreadFunc(void* p) { continue; } - // ApiResult result; - // if (data->m_api.IsUpload()) { - // result = UploadInternal(data->m_curl, data->m_api); - // } else { - // result = DownloadInternal(data->m_curl, data->m_api); - // } const auto result = data->m_api.IsUpload() ? UploadInternal(data->m_curl, data->m_api) : DownloadInternal(data->m_curl, data->m_api); if (g_running && data->m_api.GetOnComplete() && !data->m_api.GetToken().stop_requested()) { evman::push( @@ -956,6 +931,11 @@ auto Init() -> bool { } } + g_curl_single = curl_easy_init(); + if (!g_curl_single) { + log_write("failed to create g_curl_single\n"); + } + log_write("finished creating threads\n"); if (!g_cache.init()) { @@ -970,6 +950,11 @@ void Exit() { g_thread_queue.Close(); + if (g_curl_single) { + curl_easy_cleanup(g_curl_single); + g_curl_single = nullptr; + } + for (auto& entry : g_threads) { entry.Close(); } @@ -987,28 +972,28 @@ auto ToMemory(const Api& e) -> ApiResult { if (!e.GetPath().empty()) { return {}; } - return DownloadInternal(e); + return DownloadInternal(g_curl_single, e); } auto ToFile(const Api& e) -> ApiResult { if (e.GetPath().empty()) { return {}; } - return DownloadInternal(e); + return DownloadInternal(g_curl_single, e); } auto FromMemory(const Api& e) -> ApiResult { if (!e.GetPath().empty()) { return {}; } - return UploadInternal(e); + return UploadInternal(g_curl_single, e); } auto FromFile(const Api& e) -> ApiResult { if (e.GetPath().empty()) { return {}; } - return UploadInternal(e); + return UploadInternal(g_curl_single, e); } auto ToMemoryAsync(const Api& api) -> bool { diff --git a/sphaira/source/ui/menus/filebrowser.cpp b/sphaira/source/ui/menus/filebrowser.cpp index 0920617..a10e9ba 100644 --- a/sphaira/source/ui/menus/filebrowser.cpp +++ b/sphaira/source/ui/menus/filebrowser.cpp @@ -18,6 +18,8 @@ #include "owo.hpp" #include "swkbd.hpp" #include "i18n.hpp" +#include "location.hpp" + #include "yati/yati.hpp" #include "yati/source/file.hpp" @@ -337,7 +339,7 @@ Menu::Menu(const std::vector& nro_entries) : MenuBase{"FileBrowser"_i1 } })); } else if (App::GetInstallEnable() && IsExtension(entry.GetExtension(), INSTALL_EXTENSIONS)) { - InstallFile(GetEntry()); + InstallFiles(); } else { const auto assoc_list = FindFileAssocFor(); if (!assoc_list.empty()) { @@ -427,27 +429,16 @@ Menu::Menu(const std::vector& nro_entries) : MenuBase{"FileBrowser"_i1 if (m_entries_current.size()) { options->Add(std::make_shared("Cut"_i18n, [this](){ - if (!m_selected_count) { - AddCurrentFileToSelection(SelectedType::Cut); - } else { - AddSelectedEntries(SelectedType::Cut); - } + AddSelectedEntries(SelectedType::Cut); }, true)); options->Add(std::make_shared("Copy"_i18n, [this](){ - if (!m_selected_count) { - AddCurrentFileToSelection(SelectedType::Copy); - } else { - AddSelectedEntries(SelectedType::Copy); - } + AddSelectedEntries(SelectedType::Copy); }, true)); options->Add(std::make_shared("Delete"_i18n, [this](){ - if (!m_selected_count) { - AddCurrentFileToSelection(SelectedType::Delete); - } else { - AddSelectedEntries(SelectedType::Delete); - } + AddSelectedEntries(SelectedType::Delete); + log_write("clicked on delete\n"); App::Push(std::make_shared( "Delete Selected files?"_i18n, "No"_i18n, "Yes"_i18n, 0, [this](auto op_index){ @@ -522,11 +513,7 @@ Menu::Menu(const std::vector& nro_entries) : MenuBase{"FileBrowser"_i1 if (m_entries_current.size() && App::GetInstallEnable()) { if (check_all_ext(INSTALL_EXTENSIONS)) { options->Add(std::make_shared("Install"_i18n, [this](){ - if (!m_selected_count) { - InstallFile(GetEntry()); - } else { - InstallFiles(GetSelectedEntries()); - } + InstallFiles(); })); } } @@ -557,22 +544,14 @@ Menu::Menu(const std::vector& nro_entries) : MenuBase{"FileBrowser"_i1 ON_SCOPE_EXIT(App::Push(options)); options->Add(std::make_shared("Extract here"_i18n, [this](){ - if (!m_selected_count) { - UnzipFile("", GetEntry()); - } else { - UnzipFiles("", GetSelectedEntries()); - } + UnzipFiles(""); })); options->Add(std::make_shared("Extract to root"_i18n, [this](){ App::Push(std::make_shared("Are you sure you want to extract to root?"_i18n, "No"_i18n, "Yes"_i18n, 0, [this](auto op_index){ if (op_index && *op_index) { - if (!m_selected_count) { - UnzipFile("/", GetEntry()); - } else { - UnzipFiles("/", GetSelectedEntries()); - } + UnzipFiles("/"); } })); })); @@ -580,11 +559,7 @@ Menu::Menu(const std::vector& nro_entries) : MenuBase{"FileBrowser"_i1 options->Add(std::make_shared("Extract to..."_i18n, [this](){ std::string out; if (R_SUCCEEDED(swkbd::ShowText(out, "Enter the path to the folder to extract into", fs::AppendPath(m_path, ""))) && !out.empty()) { - if (!m_selected_count) { - UnzipFile(out, GetEntry()); - } else { - UnzipFiles(out, GetSelectedEntries()); - } + UnzipFiles(out); } })); })); @@ -596,21 +571,13 @@ Menu::Menu(const std::vector& nro_entries) : MenuBase{"FileBrowser"_i1 ON_SCOPE_EXIT(App::Push(options)); options->Add(std::make_shared("Compress"_i18n, [this](){ - if (!m_selected_count) { - ZipFile("", GetEntry()); - } else { - ZipFiles("", GetSelectedEntries()); - } + ZipFiles(""); })); options->Add(std::make_shared("Compress to..."_i18n, [this](){ std::string out; if (R_SUCCEEDED(swkbd::ShowText(out, "Enter the path to the folder to extract into", m_path)) && !out.empty()) { - if (!m_selected_count) { - ZipFile(out, GetEntry()); - } else { - ZipFiles(out, GetSelectedEntries()); - } + ZipFiles(out); } })); })); @@ -670,6 +637,12 @@ Menu::Menu(const std::vector& nro_entries) : MenuBase{"FileBrowser"_i1 })); } + if (m_fs_type == FsType::Sd && m_entries_current.size()) { + options->Add(std::make_shared("Upload"_i18n, [this](){ + UploadFiles(); + })); + } + options->Add(std::make_shared("Ignore read only"_i18n, m_ignore_read_only.Get(), [this](bool& v_out){ m_ignore_read_only.Set(v_out); m_fs->SetIgnoreReadOnly(v_out); @@ -918,12 +891,9 @@ void Menu::InstallForwarder() { )); } -void Menu::InstallFile(const FileEntry& target) { - std::vector targets{target}; - InstallFiles(targets); -} +void Menu::InstallFiles() { + const auto targets = GetSelectedEntries(); -void Menu::InstallFiles(const std::vector& targets) { App::Push(std::make_shared("Install Selected files?"_i18n, "No"_i18n, "Yes"_i18n, 0, [this, targets](auto op_index){ if (op_index && *op_index) { App::PopToMenu(); @@ -946,12 +916,9 @@ void Menu::InstallFiles(const std::vector& targets) { })); } -void Menu::UnzipFile(const fs::FsPath& dir_path, const FileEntry& target) { - std::vector targets{target}; - UnzipFiles(dir_path, targets); -} +void Menu::UnzipFiles(fs::FsPath dir_path) { + const auto targets = GetSelectedEntries(); -void Menu::UnzipFiles(fs::FsPath dir_path, const std::vector& targets) { // set to current path. if (dir_path.empty()) { dir_path = m_path; @@ -1058,12 +1025,9 @@ void Menu::UnzipFiles(fs::FsPath dir_path, const std::vector& targets })); } -void Menu::ZipFile(const fs::FsPath& zip_path, const FileEntry& target) { - std::vector targets{target}; - ZipFiles(zip_path, targets); -} +void Menu::ZipFiles(fs::FsPath zip_out) { + const auto targets = GetSelectedEntries(); -void Menu::ZipFiles(fs::FsPath zip_out, const std::vector& targets) { // set to current path. if (zip_out.empty()) { if (std::size(targets) == 1) { @@ -1212,6 +1176,83 @@ void Menu::ZipFiles(fs::FsPath zip_out, const std::vector& targets) { })); } +void Menu::UploadFiles() { + const auto targets = GetSelectedEntries(); + + const auto network_locations = location::Load(); + if (network_locations.empty()) { + App::Notify("No upload locations set!"); + return; + } + + PopupList::Items items; + for (const auto&p : network_locations) { + items.emplace_back(p.name); + } + + App::Push(std::make_shared( + "Select upload location"_i18n, items, [this, network_locations](auto op_index){ + if (!op_index) { + return; + } + + const auto loc = network_locations[*op_index]; + App::Push(std::make_shared(0, "Uploading"_i18n, "", [this, loc](auto pbox) -> bool { + auto targets = GetSelectedEntries(); + + const auto file_add = [&](const fs::FsPath& file_path, const char* name){ + // 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); + + const auto result = curl::Api().FromFile( + CURL_LOCATION_TO_API(loc), + curl::Path{file_path}, + curl::OnProgress{pbox->OnDownloadProgressCallback()}, + curl::UploadInfo{relative_file_name} + ); + + return result.success; + }; + + for (auto& e : targets) { + if (e.IsFile()) { + const auto file_path = GetNewPath(e); + if (!file_add(file_path, e.GetName().c_str())) { + return false; + } + } else { + FsDirCollections collections; + get_collections(GetNewPath(e), e.name, collections); + + for (const auto& collection : collections) { + for (const auto& file : collection.files) { + const auto file_path = fs::AppendPath(collection.path, file.name); + if (!file_add(file_path, file.name)) { + return false; + } + } + } + } + } + + return true; + }, [this](bool success){ + ResetSelection(); + + if (success) { + App::Notify("Upload successfull!"); + log_write("Upload successfull!!!\n"); + } else { + App::Notify("Upload failed!"); + log_write("Upload failed!!!\n"); + } + })); + } + )); +} + auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result { log_write("new scan path: %s\n", new_path.s); if (!is_walk_up && !m_path.empty() && !m_entries_current.empty()) { diff --git a/sphaira/source/ui/menus/game_menu.cpp b/sphaira/source/ui/menus/game_menu.cpp index f6bcd0d..852c78d 100644 --- a/sphaira/source/ui/menus/game_menu.cpp +++ b/sphaira/source/ui/menus/game_menu.cpp @@ -408,12 +408,7 @@ Result DumpNspToNetwork(ProgressBox* pbox, const location::Entry& loc, std::span s64 offset{}; const auto result = curl::Api().FromMemory( - curl::Url{loc.url}, - curl::UserPass{loc.user, loc.pass}, - curl::Bearer{loc.bearer}, - curl::PubKey{loc.pub_key}, - curl::PrivKey{loc.priv_key}, - curl::Port(loc.port), + CURL_LOCATION_TO_API(loc), curl::OnProgress{pbox->OnDownloadProgressCallback()}, curl::UploadInfo{ e.path, e.nsp_size,