ghdl: list all releases
This commit is contained in:
@@ -66,6 +66,8 @@ private:
|
|||||||
void Sort();
|
void Sort();
|
||||||
void UpdateSubheading();
|
void UpdateSubheading();
|
||||||
|
|
||||||
|
void DownloadEntries();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Entry> m_entries{};
|
std::vector<Entry> m_entries{};
|
||||||
s64 m_index{};
|
s64 m_index{};
|
||||||
|
|||||||
@@ -142,3 +142,18 @@ constexpr auto cexprHash(const char *str, std::size_t v = 0) noexcept -> std::si
|
|||||||
__VA_ARGS__ \
|
__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]); \
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ namespace {
|
|||||||
constexpr auto CACHE_PATH = "/switch/sphaira/cache/github";
|
constexpr auto CACHE_PATH = "/switch/sphaira/cache/github";
|
||||||
|
|
||||||
auto GenerateApiUrl(const Entry& e) {
|
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";
|
return "https://api.github.com/repos/" + e.owner + "/" + e.repo + "/releases/latest";
|
||||||
} else {
|
} else {
|
||||||
return "https://api.github.com/repos/" + e.owner + "/" + e.repo + "/releases/tags/" + e.tag;
|
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) {
|
// void from_json(const fs::FsPath& path, GhApiEntry& e) {
|
||||||
JSON_INIT_VEC_FILE(path, nullptr, nullptr);
|
// 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_OBJ_ITR(
|
||||||
JSON_SET_STR(tag_name);
|
JSON_SET_STR(tag_name);
|
||||||
JSON_SET_STR(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<GhApiEntry>& 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 {
|
auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry* entry) -> Result {
|
||||||
static const fs::FsPath temp_file{"/switch/sphaira/cache/github/ghdl.temp"};
|
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();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto DownloadAssetJson(ProgressBox* pbox, const std::string& url, GhApiEntry& out) -> Result {
|
auto DownloadReleaseJsonJson(ProgressBox* pbox, const std::string& url, std::vector<GhApiEntry>& out) -> Result {
|
||||||
// 1. download the json
|
// 1. download the json
|
||||||
if (!pbox->ShouldExit()) {
|
if (!pbox->ShouldExit()) {
|
||||||
pbox->NewTransfer("Downloading json"_i18n);
|
pbox->NewTransfer("Downloading json"_i18n);
|
||||||
@@ -147,7 +167,7 @@ auto DownloadAssetJson(ProgressBox* pbox, const std::string& url, GhApiEntry& ou
|
|||||||
from_json(result.path, out);
|
from_json(result.path, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
R_UNLESS(!out.assets.empty(), Result_GhdlEmptyAsset);
|
R_UNLESS(!out.empty(), Result_GhdlEmptyAsset);
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,95 +182,7 @@ Menu::Menu(u32 flags) : MenuBase{"GitHub"_i18n, flags} {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hack
|
DownloadEntries();
|
||||||
static GhApiEntry gh_entry;
|
|
||||||
gh_entry = {};
|
|
||||||
|
|
||||||
App::Push<ProgressBox>(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<const AssetEntry*> asset_ptr;
|
|
||||||
std::vector<GhApiAsset> 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<PopupList>("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<ProgressBox>(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<OptionBox>(post_install_message, "OK"_i18n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!pre_install_message.empty()) {
|
|
||||||
App::Push<OptionBox>(
|
|
||||||
pre_install_message,
|
|
||||||
"Back"_i18n, "Download"_i18n, 1, [this, func](auto op_index){
|
|
||||||
if (op_index && *op_index) {
|
|
||||||
func();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
func();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}}),
|
}}),
|
||||||
|
|
||||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
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());
|
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);
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.tag.empty()) {
|
|
||||||
entry.tag = "latest";
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.json_path = full_path;
|
entry.json_path = full_path;
|
||||||
m_entries.emplace_back(entry);
|
m_entries.emplace_back(entry);
|
||||||
}
|
}
|
||||||
@@ -417,4 +347,109 @@ void Menu::UpdateSubheading() {
|
|||||||
this->SetSubHeading(std::to_string(index) + " / " + std::to_string(m_entries.size()));
|
this->SetSubHeading(std::to_string(index) + " / " + std::to_string(m_entries.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::DownloadEntries() {
|
||||||
|
// hack
|
||||||
|
static std::vector<GhApiEntry> gh_entries;
|
||||||
|
gh_entries = {};
|
||||||
|
|
||||||
|
App::Push<ProgressBox>(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<PopupList>("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<const AssetEntry*> asset_ptr;
|
||||||
|
std::vector<GhApiAsset> 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<PopupList>("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<ProgressBox>(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<OptionBox>(post_install_message, "OK"_i18n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!pre_install_message.empty()) {
|
||||||
|
App::Push<OptionBox>(
|
||||||
|
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
|
} // namespace sphaira::ui::menu::gh
|
||||||
|
|||||||
Reference in New Issue
Block a user