diff --git a/sphaira/include/ui/menus/ghdl.hpp b/sphaira/include/ui/menus/ghdl.hpp index edc85a9..daf2a5e 100644 --- a/sphaira/include/ui/menus/ghdl.hpp +++ b/sphaira/include/ui/menus/ghdl.hpp @@ -66,6 +66,8 @@ private: void Sort(); void UpdateSubheading(); + void DownloadEntries(); + private: std::vector m_entries{}; s64 m_index{}; diff --git a/sphaira/include/yyjson_helper.hpp b/sphaira/include/yyjson_helper.hpp index 5a543f5..8c4895b 100644 --- a/sphaira/include/yyjson_helper.hpp +++ b/sphaira/include/yyjson_helper.hpp @@ -142,3 +142,18 @@ constexpr auto cexprHash(const char *str, std::size_t v = 0) noexcept -> std::si __VA_ARGS__ \ } \ } + +#define JSON_ARR_ITR(member) \ + if (!yyjson_is_arr(json)) { \ + return; \ + } \ + const auto arr_size = yyjson_arr_size(json); \ + if (!arr_size) { \ + return; \ + } \ + member.resize(arr_size); \ + size_t idx, max; \ + yyjson_val *hit; \ + yyjson_arr_foreach(json, idx, max, hit) { \ + from_json(hit, member[idx]); \ + } diff --git a/sphaira/source/ui/menus/ghdl.cpp b/sphaira/source/ui/menus/ghdl.cpp index d43ca69..4d08e19 100644 --- a/sphaira/source/ui/menus/ghdl.cpp +++ b/sphaira/source/ui/menus/ghdl.cpp @@ -29,7 +29,9 @@ namespace { constexpr auto CACHE_PATH = "/switch/sphaira/cache/github"; auto GenerateApiUrl(const Entry& e) { - if (e.tag == "latest") { + if (e.tag.empty()) { + return "https://api.github.com/repos/" + e.owner + "/" + e.repo + "/releases"; + } else if (e.tag == "latest") { return "https://api.github.com/repos/" + e.owner + "/" + e.repo + "/releases/latest"; } else { return "https://api.github.com/repos/" + e.owner + "/" + e.repo + "/releases/tags/" + e.tag; @@ -74,8 +76,16 @@ void from_json(yyjson_val* json, GhApiAsset& e) { ); } -void from_json(const fs::FsPath& path, GhApiEntry& e) { - JSON_INIT_VEC_FILE(path, nullptr, nullptr); +// void from_json(const fs::FsPath& path, GhApiEntry& e) { +// JSON_INIT_VEC_FILE(path, nullptr, nullptr); +// JSON_OBJ_ITR( +// JSON_SET_STR(tag_name); +// JSON_SET_STR(name); +// JSON_SET_ARR_OBJ(assets); +// ); +// } + +void from_json(yyjson_val* json, GhApiEntry& e) { JSON_OBJ_ITR( JSON_SET_STR(tag_name); JSON_SET_STR(name); @@ -83,6 +93,16 @@ void from_json(const fs::FsPath& path, GhApiEntry& e) { ); } +void from_json(const fs::FsPath& path, std::vector& e) { + JSON_INIT_VEC_FILE(path, nullptr, nullptr); + if (yyjson_is_arr(json)) { + JSON_ARR_ITR(e); + } else { + e.resize(1); + from_json(json, e[0]); + } +} + auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry* entry) -> Result { static const fs::FsPath temp_file{"/switch/sphaira/cache/github/ghdl.temp"}; @@ -125,7 +145,7 @@ auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry R_SUCCEED(); } -auto DownloadAssetJson(ProgressBox* pbox, const std::string& url, GhApiEntry& out) -> Result { +auto DownloadReleaseJsonJson(ProgressBox* pbox, const std::string& url, std::vector& out) -> Result { // 1. download the json if (!pbox->ShouldExit()) { pbox->NewTransfer("Downloading json"_i18n); @@ -147,7 +167,7 @@ auto DownloadAssetJson(ProgressBox* pbox, const std::string& url, GhApiEntry& ou from_json(result.path, out); } - R_UNLESS(!out.assets.empty(), Result_GhdlEmptyAsset); + R_UNLESS(!out.empty(), Result_GhdlEmptyAsset); R_SUCCEED(); } @@ -162,95 +182,7 @@ Menu::Menu(u32 flags) : MenuBase{"GitHub"_i18n, flags} { return; } - // hack - static GhApiEntry gh_entry; - gh_entry = {}; - - App::Push(0, "Downloading "_i18n, GetEntry().repo, [this](auto pbox) -> Result { - return DownloadAssetJson(pbox, GenerateApiUrl(GetEntry()), gh_entry); - }, [this](Result rc){ - App::PushErrorBox(rc, "Failed to download json"_i18n); - - if (R_SUCCEEDED(rc)) { - const auto& assets = GetEntry().assets; - PopupList::Items asset_items; - std::vector asset_ptr; - std::vector api_assets; - bool using_name = false; - - for (auto&p : gh_entry.assets) { - bool found = false; - - for (auto& e : assets) { - if (!e.name.empty()) { - using_name = true; - } - - if (p.name.find(e.name) != p.name.npos) { - found = true; - asset_ptr.emplace_back(&e); - break; - } - } - - if (!using_name || found) { - asset_items.emplace_back(p.name); - api_assets.emplace_back(p); - } - } - - App::Push("Select asset to download for "_i18n + GetEntry().repo, asset_items, [this, api_assets, asset_ptr](auto op_index){ - if (!op_index) { - return; - } - - const auto index = *op_index; - const auto& asset_entry = api_assets[index]; - const AssetEntry* ptr{}; - auto pre_install_message = GetEntry().pre_install_message; - if (asset_ptr.size()) { - ptr = asset_ptr[index]; - if (!ptr->pre_install_message.empty()) { - pre_install_message = ptr->pre_install_message; - } - } - - const auto func = [this, &asset_entry, ptr](){ - App::Push(0, "Downloading "_i18n, GetEntry().repo, [this, &asset_entry, ptr](auto pbox) -> Result { - return DownloadApp(pbox, asset_entry, ptr); - }, [this, ptr](Result rc){ - homebrew::SignalChange(); - App::PushErrorBox(rc, "Failed to download app!"_i18n); - - if (R_SUCCEEDED(rc)) { - App::Notify("Downloaded "_i18n + GetEntry().repo); - auto post_install_message = GetEntry().post_install_message; - if (ptr && !ptr->post_install_message.empty()) { - post_install_message = ptr->post_install_message; - } - - if (!post_install_message.empty()) { - App::Push(post_install_message, "OK"_i18n); - } - } - }); - }; - - if (!pre_install_message.empty()) { - App::Push( - pre_install_message, - "Back"_i18n, "Download"_i18n, 1, [this, func](auto op_index){ - if (op_index && *op_index) { - func(); - } - } - ); - } else { - func(); - } - }); - } - }); + DownloadEntries(); }}), std::make_pair(Button::B, Action{"Back"_i18n, [this](){ @@ -308,7 +240,9 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) { gfx::drawTextArgs(vg, x + text_xoffset, y + (h / 2.f), 20.f, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(text_id), "%s By %s", e.repo.c_str(), e.owner.c_str()); nvgRestore(vg); - gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f), 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE, theme->GetColour(text_id), "version: %s", e.tag.c_str()); + if (!e.tag.empty()) { + gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f), 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE, theme->GetColour(text_id), "version: %s", e.tag.c_str()); + } }); } @@ -384,10 +318,6 @@ void Menu::LoadEntriesFromPath(const fs::FsPath& path) { continue; } - if (entry.tag.empty()) { - entry.tag = "latest"; - } - entry.json_path = full_path; m_entries.emplace_back(entry); } @@ -417,4 +347,109 @@ void Menu::UpdateSubheading() { this->SetSubHeading(std::to_string(index) + " / " + std::to_string(m_entries.size())); } +void Menu::DownloadEntries() { + // hack + static std::vector gh_entries; + gh_entries = {}; + + App::Push(0, "Downloading "_i18n, GetEntry().repo, [this](auto pbox) -> Result { + return DownloadReleaseJsonJson(pbox, GenerateApiUrl(GetEntry()), gh_entries); + }, [this](Result rc){ + App::PushErrorBox(rc, "Failed to download json"_i18n); + if (R_FAILED(rc) || gh_entries.empty()) { + return; + } + + PopupList::Items entry_items; + for (const auto& e : gh_entries) { + entry_items.emplace_back(e.name); + } + + App::Push("Select release to download for "_i18n + GetEntry().repo, entry_items, [this](auto op_index){ + if (!op_index) { + return; + } + + const auto& gh_entry = gh_entries[*op_index]; + const auto& assets = GetEntry().assets; + PopupList::Items asset_items; + std::vector asset_ptr; + std::vector api_assets; + bool using_name = false; + + for (auto&p : gh_entry.assets) { + bool found = false; + + for (auto& e : assets) { + if (!e.name.empty()) { + using_name = true; + } + + if (p.name.find(e.name) != p.name.npos) { + found = true; + asset_ptr.emplace_back(&e); + break; + } + } + + if (!using_name || found) { + asset_items.emplace_back(p.name); + api_assets.emplace_back(p); + } + } + + App::Push("Select asset to download for "_i18n + GetEntry().repo, asset_items, [this, api_assets, asset_ptr](auto op_index){ + if (!op_index) { + return; + } + + const auto index = *op_index; + const auto& asset_entry = api_assets[index]; + const AssetEntry* ptr{}; + auto pre_install_message = GetEntry().pre_install_message; + if (asset_ptr.size()) { + ptr = asset_ptr[index]; + if (!ptr->pre_install_message.empty()) { + pre_install_message = ptr->pre_install_message; + } + } + + const auto func = [this, &asset_entry, ptr](){ + App::Push(0, "Downloading "_i18n, GetEntry().repo, [this, &asset_entry, ptr](auto pbox) -> Result { + return DownloadApp(pbox, asset_entry, ptr); + }, [this, ptr](Result rc){ + homebrew::SignalChange(); + App::PushErrorBox(rc, "Failed to download app!"_i18n); + + if (R_SUCCEEDED(rc)) { + App::Notify("Downloaded "_i18n + GetEntry().repo); + auto post_install_message = GetEntry().post_install_message; + if (ptr && !ptr->post_install_message.empty()) { + post_install_message = ptr->post_install_message; + } + + if (!post_install_message.empty()) { + App::Push(post_install_message, "OK"_i18n); + } + } + }); + }; + + if (!pre_install_message.empty()) { + App::Push( + pre_install_message, + "Back"_i18n, "Download"_i18n, 1, [this, func](auto op_index){ + if (op_index && *op_index) { + func(); + } + } + ); + } else { + func(); + } + }); + }); + }); +} + } // namespace sphaira::ui::menu::gh