diff --git a/sphaira/include/download.hpp b/sphaira/include/download.hpp index a6951ac..0c2e6eb 100644 --- a/sphaira/include/download.hpp +++ b/sphaira/include/download.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace sphaira::curl { @@ -29,6 +30,7 @@ struct ApiResult; using Path = fs::FsPath; using OnComplete = std::function; using OnProgress = std::function; +using StopToken = std::stop_token; struct Url { Url() = default; @@ -71,6 +73,7 @@ struct ApiResult { struct DownloadEventData { OnComplete callback; ApiResult result; + StopToken stoken; }; auto Init() -> bool; @@ -114,6 +117,7 @@ struct Api { auto ToMemory(Ts&&... ts) { static_assert(std::disjunction_v...>, "Url must be specified"); static_assert(!std::disjunction_v...>, "Path must not valid for memory"); + static_assert(!std::disjunction_v...>, "OnComplete must not be specified"); Api::set_option(std::forward(ts)...); return curl::ToMemory(*this); } @@ -122,6 +126,7 @@ struct Api { auto ToFile(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...>, "OnComplete must not be specified"); Api::set_option(std::forward(ts)...); return curl::ToFile(*this); } @@ -131,6 +136,7 @@ struct Api { static_assert(std::disjunction_v...>, "Url must be specified"); static_assert(std::disjunction_v...>, "OnComplete must be specified"); static_assert(!std::disjunction_v...>, "Path must not valid for memory"); + static_assert(std::disjunction_v...>, "StopToken must be specified"); Api::set_option(std::forward(ts)...); return curl::ToMemoryAsync(*this); } @@ -140,18 +146,38 @@ struct Api { static_assert(std::disjunction_v...>, "Url must be specified"); static_assert(std::disjunction_v...>, "Path 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)...); return curl::ToFileAsync(*this); } - Url m_url; - Fields m_fields{}; - Header m_header{}; - Flags m_flags{}; - Path m_path{}; - OnComplete m_on_complete = nullptr; - OnProgress m_on_progress = nullptr; - Priority m_prio = Priority::High; + auto& GetUrl() const { + return m_url.m_str; + } + auto& GetFields() const { + return m_fields.m_str; + } + auto& GetHeader() const { + return m_header; + } + auto& GetFlags() const { + return m_flags.m_flags; + } + auto& GetPath() const { + return m_path; + } + auto& GetOnComplete() const { + return m_on_complete; + } + auto& GetOnProgress() const { + return m_on_progress; + } + auto& GetPriority() const { + return m_prio; + } + auto& GetToken() const { + return m_stoken; + } private: void SetOption(Url&& v) { @@ -178,6 +204,9 @@ private: void SetOption(Priority&& v) { m_prio = v; } + void SetOption(StopToken&& v) { + m_stoken = v; + } template void set_option(T&& t) { @@ -189,6 +218,18 @@ private: set_option(std::forward(t)); set_option(std::forward(ts)...); } + +private: + Url m_url; + Fields m_fields{}; + Header m_header{}; + Flags m_flags{}; + Path m_path{}; + OnComplete m_on_complete{nullptr}; + OnProgress m_on_progress{nullptr}; + Priority m_prio{Priority::High}; + std::stop_source m_stop_source{}; + StopToken m_stoken{m_stop_source.get_token()}; }; } // namespace sphaira::curl diff --git a/sphaira/include/ui/object.hpp b/sphaira/include/ui/object.hpp index 8aa5445..fae55e5 100644 --- a/sphaira/include/ui/object.hpp +++ b/sphaira/include/ui/object.hpp @@ -1,13 +1,16 @@ #pragma once #include "types.hpp" +#include namespace sphaira::ui { class Object { public: Object() = default; - virtual ~Object() = default; + virtual ~Object() { + m_stop_source.request_stop(); + } virtual auto Draw(NVGcontext* vg, Theme* theme) -> void = 0; @@ -71,8 +74,14 @@ public: m_hidden = value; } + auto GetToken() const { + return m_stop_source.get_token(); + } + protected: Vec4 m_pos{}; + // used for lifetime management across threads. + std::stop_source m_stop_source{}; bool m_hidden{false}; }; diff --git a/sphaira/source/app.cpp b/sphaira/source/app.cpp index 9ff3bf2..d1e2092 100644 --- a/sphaira/source/app.cpp +++ b/sphaira/source/app.cpp @@ -421,7 +421,9 @@ void App::Loop() { } } else if constexpr(std::is_same_v) { log_write("[DownloadEventData] got event\n"); - arg.callback(arg.result); + if (arg.callback && !arg.stoken.stop_requested()) { + arg.callback(arg.result); + } } else { static_assert(false, "non-exhaustive visitor!"); } diff --git a/sphaira/source/download.cpp b/sphaira/source/download.cpp index 978a637..9104c96 100644 --- a/sphaira/source/download.cpp +++ b/sphaira/source/download.cpp @@ -308,7 +308,7 @@ struct ThreadQueue { ThreadQueueEntry entry{}; entry.api = api; - switch (api.m_prio) { + switch (api.GetPriority()) { case Priority::Normal: m_entries.emplace_back(entry); break; @@ -350,13 +350,13 @@ auto ProgressCallbackFunc1(void *clientp, curl_off_t dltotal, curl_off_t dlnow, } auto ProgressCallbackFunc2(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -> size_t { - if (!g_running) { + auto api = static_cast(clientp); + if (!g_running || api->GetToken().stop_requested()) { return 1; } // log_write("pcall called %u %u %u %u\n", dltotal, dlnow, ultotal, ulnow); - auto callback = *static_cast(clientp); - if (!callback(dltotal, dlnow, ultotal, ulnow)) { + if (!api->GetOnProgress()(dltotal, dlnow, ultotal, ulnow)) { return 1; } @@ -444,11 +444,11 @@ auto header_callback(char* b, size_t size, size_t nitems, void* userdata) -> siz auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult { fs::FsPath tmp_buf; - const bool has_file = !e.m_path.empty() && e.m_path != ""; - const bool has_post = !e.m_fields.m_str.empty() && e.m_fields.m_str != ""; + const bool has_file = !e.GetPath().empty() && e.GetPath() != ""; + const bool has_post = !e.GetFields().empty() && e.GetFields() != ""; DataStruct chunk; - Header header_in = e.m_header; + Header header_in = e.GetHeader(); Header header_out; fs::FsNativeSd fs; @@ -466,8 +466,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult { return {}; } - if (e.m_flags.m_flags & Flag_Cache) { - g_cache.get(e.m_path, header_in); + if (e.GetFlags() & Flag_Cache) { + g_cache.get(e.GetPath(), header_in); } } @@ -475,7 +475,7 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult { chunk.data.reserve(CHUNK_SIZE); curl_easy_reset(curl); - CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, e.m_url.m_str.c_str()); + CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, e.GetUrl().c_str()); CURL_EASY_SETOPT_LOG(curl, CURLOPT_USERAGENT, "TotalJustice"); CURL_EASY_SETOPT_LOG(curl, CURLOPT_FOLLOWLOCATION, 1L); CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYPEER, 0L); @@ -487,8 +487,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult { CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERDATA, &header_out); if (has_post) { - CURL_EASY_SETOPT_LOG(curl, CURLOPT_POSTFIELDS, e.m_fields.m_str.c_str()); - log_write("setting post field: %s\n", e.m_fields.m_str.c_str()); + CURL_EASY_SETOPT_LOG(curl, CURLOPT_POSTFIELDS, e.GetFields().c_str()); + log_write("setting post field: %s\n", e.GetFields().c_str()); } struct curl_slist* list = NULL; @@ -517,8 +517,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult { } // progress calls. - if (e.m_on_progress) { - CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &e.m_on_progress); + if (e.GetOnProgress()) { + CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &e); CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc2); } else { CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc1); @@ -546,16 +546,16 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult { if (res == CURLE_OK) { if (http_code == 304) { - log_write("cached download: %s\n", e.m_url.m_str.c_str()); + log_write("cached download: %s\n", e.GetUrl().c_str()); } else { - log_write("un-cached download: %s code: %u\n", e.m_url.m_str.c_str(), http_code); - if (e.m_flags.m_flags & Flag_Cache) { - g_cache.set(e.m_path, header_out); + log_write("un-cached download: %s code: %u\n", e.GetUrl().c_str(), http_code); + if (e.GetFlags() & Flag_Cache) { + g_cache.set(e.GetPath(), header_out); } - fs.DeleteFile(e.m_path); - fs.CreateDirectoryRecursivelyWithPath(e.m_path); - if (R_FAILED(fs.RenameFile(tmp_buf, e.m_path))) { + fs.DeleteFile(e.GetPath()); + fs.CreateDirectoryRecursivelyWithPath(e.GetPath()); + if (R_FAILED(fs.RenameFile(tmp_buf, e.GetPath()))) { success = false; } } @@ -568,8 +568,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult { } } - log_write("Downloaded %s %s\n", e.m_url.m_str.c_str(), curl_easy_strerror(res)); - return {success, http_code, header_out, chunk.data, e.m_path}; + log_write("Downloaded %s %s\n", e.GetUrl().c_str(), curl_easy_strerror(res)); + return {success, http_code, header_out, chunk.data, e.GetPath()}; } auto DownloadInternal(const Api& e) -> ApiResult { @@ -604,8 +604,8 @@ void ThreadEntry::ThreadFunc(void* p) { #if 1 const auto result = DownloadInternal(data->m_curl, data->m_api); - if (g_running) { - const DownloadEventData event_data{data->m_api.m_on_complete, result}; + if (g_running && data->m_api.GetOnComplete() && !data->m_api.GetToken().stop_requested()) { + const DownloadEventData event_data{data->m_api.GetOnComplete(), result, data->m_api.GetToken()}; evman::push(std::move(event_data), false); } else { break; @@ -736,14 +736,14 @@ void Exit() { } auto ToMemory(const Api& e) -> ApiResult { - if (!e.m_path.empty()) { + if (!e.GetPath().empty()) { return {}; } return DownloadInternal(e); } auto ToFile(const Api& e) -> ApiResult { - if (e.m_path.empty()) { + if (e.GetPath().empty()) { return {}; } return DownloadInternal(e); diff --git a/sphaira/source/ui/menus/appstore.cpp b/sphaira/source/ui/menus/appstore.cpp index c7ece08..74016e3 100644 --- a/sphaira/source/ui/menus/appstore.cpp +++ b/sphaira/source/ui/menus/appstore.cpp @@ -663,6 +663,7 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu) curl::Url{URL_POST_FEEDBACK}, curl::Path{file}, curl::Fields{post}, + curl::StopToken{this->GetToken()}, curl::OnComplete{[](auto& result){ if (result.success) { log_write("got feedback!\n"); @@ -697,6 +698,7 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu) curl::Url{url}, curl::Path{path}, curl::Flags{curl::Flag_Cache}, + curl::StopToken{this->GetToken()}, curl::OnComplete{[this, path](auto& result){ if (result.success) { if (result.code == 304) { @@ -990,6 +992,7 @@ Menu::Menu(const std::vector& nro_entries) : MenuBase{"AppStore"_i18n} curl::Url{URL_JSON}, curl::Path{REPO_PATH}, curl::Flags{curl::Flag_Cache}, + curl::StopToken{this->GetToken()}, curl::OnComplete{[this](auto& result){ if (result.success) { m_repo_download_state = ImageDownloadState::Done; @@ -1071,6 +1074,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) { curl::Url{url}, curl::Path{path}, curl::Flags{curl::Flag_Cache}, + curl::StopToken{this->GetToken()}, curl::OnComplete{[this, &image](auto& result) { if (result.success) { image.state = ImageDownloadState::Done; diff --git a/sphaira/source/ui/menus/main_menu.cpp b/sphaira/source/ui/menus/main_menu.cpp index da1b210..db62f30 100644 --- a/sphaira/source/ui/menus/main_menu.cpp +++ b/sphaira/source/ui/menus/main_menu.cpp @@ -150,6 +150,7 @@ MainMenu::MainMenu() { curl::Url{GITHUB_URL}, curl::Path{CACHE_PATH}, curl::Flags{curl::Flag_Cache}, + curl::StopToken{this->GetToken()}, curl::Header{ { "Accept", "application/vnd.github+json" }, }, diff --git a/sphaira/source/ui/menus/themezer.cpp b/sphaira/source/ui/menus/themezer.cpp index 5f289f6..fc463ab 100644 --- a/sphaira/source/ui/menus/themezer.cpp +++ b/sphaira/source/ui/menus/themezer.cpp @@ -647,6 +647,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) { curl::Url{url}, curl::Path{path}, curl::Flags{curl::Flag_Cache}, + curl::StopToken{this->GetToken()}, curl::OnComplete{[this, &image](auto& result) { if (result.success) { image.state = ImageDownloadState::Done; @@ -733,6 +734,7 @@ void Menu::PackListDownload() { curl::Url{packList_url}, curl::Path{packlist_path}, curl::Flags{curl::Flag_Cache}, + curl::StopToken{this->GetToken()}, curl::OnComplete{[this, page_index](auto& result){ log_write("got themezer data\n"); if (!result.success) {