re-write download code to support headers, needed for etag support.

etag support will be added later. github supports it and themezer probably does as well.
appstore does not sadly...
This commit is contained in:
ITotalJustice
2024-12-27 02:28:44 +00:00
parent 7005118876
commit 2edfe91ad6
10 changed files with 437 additions and 335 deletions

View File

@@ -1,41 +1,159 @@
#pragma once #pragma once
#include "fs.hpp"
#include <vector> #include <vector>
#include <string> #include <string>
#include <functional> #include <functional>
#include <unordered_map>
#include <type_traits>
#include <switch.h> #include <switch.h>
namespace sphaira { namespace sphaira::curl {
using DownloadCallback = std::function<void(std::vector<u8>& data, bool success)>; enum class Priority {
using ProgressCallback = std::function<bool(u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow)>;
enum class DownloadPriority {
Normal, // gets pushed to the back of the queue Normal, // gets pushed to the back of the queue
High, // gets pushed to the front of the queue High, // gets pushed to the front of the queue
}; };
using Path = fs::FsPath;
using Header = std::unordered_map<std::string, std::string>;
using OnComplete = std::function<void(std::vector<u8>& data, bool success, long code)>;
using OnProgress = std::function<bool(u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow)>;
struct Url {
Url() = default;
Url(const std::string& str) : m_str{str} {}
std::string m_str;
};
struct Fields {
Fields() = default;
Fields(const std::string& str) : m_str{str} {}
std::string m_str;
};
struct Api;
struct DownloadEventData { struct DownloadEventData {
DownloadCallback callback; OnComplete callback;
std::vector<u8> data; std::vector<u8> data;
long code;
bool result; bool result;
}; };
auto DownloadInit() -> bool; auto Init() -> bool;
void DownloadExit(); void Exit();
// sync functions // sync functions
auto DownloadMemory(const std::string& url, const std::string& post, ProgressCallback pcallback = nullptr) -> std::vector<u8>; auto ToMemory(const Api& e) -> std::vector<u8>;
auto DownloadFile(const std::string& url, const std::string& out, const std::string& post, ProgressCallback pcallback = nullptr) -> bool; auto ToFile(const Api& e) -> bool;
// async functions // async functions
// starts the downloads in a new thread, pushes an event when complete auto ToMemoryAsync(const Api& e) -> bool;
// then, the callback will be called on the main thread. auto ToFileAsync(const Api& e) -> bool;
// auto DownloadMemoryAsync(const std::string& url, DownloadCallback callback, DownloadPriority prio = DownloadPriority::Normal) -> bool;
// auto DownloadFileAsync(const std::string& url, const std::string& out, DownloadCallback callback, DownloadPriority prio = DownloadPriority::Normal) -> bool;
auto DownloadMemoryAsync(const std::string& url, const std::string& post, DownloadCallback callback, ProgressCallback pcallback = nullptr, DownloadPriority prio = DownloadPriority::Normal) -> bool; // removes url from cache (todo: deprecate this)
auto DownloadFileAsync(const std::string& url, const std::string& out, const std::string& post, DownloadCallback callback, ProgressCallback pcallback = nullptr, DownloadPriority prio = DownloadPriority::Normal) -> bool; void ClearCache(const Url& url);
void DownloadClearCache(const std::string& url); struct Api {
Api() = default;
} // namespace sphaira template <typename... Ts>
Api(Ts&&... ts) {
Api::set_option(std::forward<Ts>(ts)...);
}
template <typename... Ts>
auto To(Ts&&... ts) {
if constexpr(std::disjunction_v<std::is_same<Path, Ts>...>) {
return ToFile(std::forward<Ts>(ts)...);
} else {
return ToMemory(std::forward<Ts>(ts)...);
}
}
template <typename... Ts>
auto ToAsync(Ts&&... ts) {
if constexpr(std::disjunction_v<std::is_same<Path, Ts>...>) {
return ToFileAsync(std::forward<Ts>(ts)...);
} else {
return ToMemoryAsync(std::forward<Ts>(ts)...);
}
}
template <typename... Ts>
auto ToMemory(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::ToMemory(*this);
}
template <typename... Ts>
auto ToFile(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::ToFile(*this);
}
template <typename... Ts>
auto ToMemoryAsync(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::ToMemoryAsync(*this);
}
template <typename... Ts>
auto ToFileAsync(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
Api::set_option(std::forward<Ts>(ts)...);
return curl::ToFileAsync(*this);
}
Url m_url;
Fields m_fields{};
Header m_header{};
Path m_path{};
OnComplete m_on_complete = nullptr;
OnProgress m_on_progress = nullptr;
Priority m_prio = Priority::Normal;
private:
void SetOption(Url&& v) {
m_url = v;
}
void SetOption(Fields&& v) {
m_fields = v;
}
void SetOption(Header&& v) {
m_header = v;
}
void SetOption(Path&& v) {
m_path = v;
}
void SetOption(OnComplete&& v) {
m_on_complete = v;
}
void SetOption(OnProgress&& v) {
m_on_progress = v;
}
void SetOption(Priority&& v) {
m_prio = v;
}
template <typename T>
void set_option(T&& t) {
SetOption(std::forward<T>(t));
}
template <typename T, typename... Ts>
void set_option(T&& t, Ts&&... ts) {
set_option(std::forward<T>(t));
set_option(std::forward<Ts>(ts)...);
}
};
} // namespace sphaira::curl

View File

@@ -26,7 +26,7 @@ using EventData = std::variant<
ExitEventData, ExitEventData,
HazeCallbackData, HazeCallbackData,
NxlinkCallbackData, NxlinkCallbackData,
DownloadEventData curl::DownloadEventData
>; >;
// returns number of events // returns number of events

View File

@@ -30,6 +30,16 @@ struct ProgressBox final : Widget {
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result; auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result;
void Yield(); void Yield();
auto OnDownloadProgressCallback() {
return [this](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){
if (this->ShouldExit()) {
return false;
}
this->UpdateTransfer(dlnow, dltotal);
return true;
};
}
public: public:
struct ThreadData { struct ThreadData {
ProgressBox* pbox; ProgressBox* pbox;

View File

@@ -276,9 +276,9 @@ void App::Loop() {
App::Notify("Nxlink Finished"_i18n); App::Notify("Nxlink Finished"_i18n);
break; break;
} }
} else if constexpr(std::is_same_v<T, DownloadEventData>) { } else if constexpr(std::is_same_v<T, curl::DownloadEventData>) {
log_write("[DownloadEventData] got event\n"); log_write("[DownloadEventData] got event\n");
arg.callback(arg.data, arg.result); arg.callback(arg.data, arg.result, arg.code);
} else { } else {
static_assert(false, "non-exhaustive visitor!"); static_assert(false, "non-exhaustive visitor!");
} }
@@ -941,7 +941,7 @@ App::App(const char* argv0) {
nxlinkInitialize(nxlink_callback); nxlinkInitialize(nxlink_callback);
} }
DownloadInit(); curl::Init();
// Create the deko3d device // Create the deko3d device
this->device = dk::DeviceMaker{} this->device = dk::DeviceMaker{}
@@ -1096,7 +1096,7 @@ App::~App() {
log_write("starting to exit\n"); log_write("starting to exit\n");
i18n::exit(); i18n::exit();
DownloadExit(); curl::Exit();
// this has to be called before any cleanup to ensure the lifetime of // this has to be called before any cleanup to ensure the lifetime of
// nvg is still active as some widgets may need to free images. // nvg is still active as some widgets may need to free images.

View File

@@ -11,9 +11,11 @@
#include <mutex> #include <mutex>
#include <curl/curl.h> #include <curl/curl.h>
namespace sphaira { namespace sphaira::curl {
namespace { namespace {
using DownloadResult = std::pair<bool, long>;
#define CURL_EASY_SETOPT_LOG(handle, opt, v) \ #define CURL_EASY_SETOPT_LOG(handle, opt, v) \
if (auto r = curl_easy_setopt(handle, opt, v); r != CURLE_OK) { \ if (auto r = curl_easy_setopt(handle, opt, v); r != CURLE_OK) { \
log_write("curl_easy_setopt(%s, %s) msg: %s\n", #opt, #v, curl_easy_strerror(r)); \ log_write("curl_easy_setopt(%s, %s) msg: %s\n", #opt, #v, curl_easy_strerror(r)); \
@@ -39,11 +41,14 @@ CURLSH* g_curl_share{};
Mutex g_mutex_share[CURL_LOCK_DATA_LAST]{}; Mutex g_mutex_share[CURL_LOCK_DATA_LAST]{};
struct UrlCache { struct UrlCache {
auto AddToCache(const std::string& url, bool force = false) { auto AddToCache(const Url& url, bool force = false) {
mutexLock(&mutex); mutexLock(&mutex);
ON_SCOPE_EXIT(mutexUnlock(&mutex)); ON_SCOPE_EXIT(mutexUnlock(&mutex));
auto it = std::find(cache.begin(), cache.end(), url); auto it = std::find_if(cache.cbegin(), cache.cend(), [&url](const auto& e){
if (it == cache.end()) { return e.m_str == url.m_str;
});
if (it == cache.cend()) {
cache.emplace_back(url); cache.emplace_back(url);
return true; return true;
} else { } else {
@@ -55,16 +60,19 @@ struct UrlCache {
} }
} }
void RemoveFromCache(const std::string& url) { void RemoveFromCache(const Url& url) {
mutexLock(&mutex); mutexLock(&mutex);
ON_SCOPE_EXIT(mutexUnlock(&mutex)); ON_SCOPE_EXIT(mutexUnlock(&mutex));
auto it = std::find(cache.begin(), cache.end(), url); auto it = std::find_if(cache.cbegin(), cache.cend(), [&url](const auto& e){
if (it != cache.end()) { return e.m_str == url.m_str;
});
if (it != cache.cend()) {
cache.erase(it); cache.erase(it);
} }
} }
std::vector<std::string> cache; std::vector<Url> cache;
Mutex mutex{}; Mutex mutex{};
}; };
@@ -101,7 +109,7 @@ struct ThreadEntry {
return m_in_progress == true; return m_in_progress == true;
} }
auto Setup(DownloadCallback callback, ProgressCallback pcallback, std::string url, std::string file, std::string post) -> bool { auto Setup(const Api& api) -> bool {
assert(m_in_progress == false && "Setting up thread while active"); assert(m_in_progress == false && "Setting up thread while active");
mutexLock(&m_mutex); mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex)); ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
@@ -109,11 +117,7 @@ struct ThreadEntry {
if (m_in_progress) { if (m_in_progress) {
return false; return false;
} }
m_url = url; m_api = api;
m_file = file;
m_post = post;
m_callback = callback;
m_pcallback = pcallback;
m_in_progress = true; m_in_progress = true;
// log_write("started download :)\n"); // log_write("started download :)\n");
ueventSignal(&m_uevent); ueventSignal(&m_uevent);
@@ -122,22 +126,14 @@ struct ThreadEntry {
CURL* m_curl{}; CURL* m_curl{};
Thread m_thread{}; Thread m_thread{};
std::string m_url{}; Api m_api{};
std::string m_file{}; // if empty, downloads to buffer
std::string m_post{}; // if empty, downloads to buffer
DownloadCallback m_callback{};
ProgressCallback m_pcallback{};
std::atomic_bool m_in_progress{}; std::atomic_bool m_in_progress{};
Mutex m_mutex{}; Mutex m_mutex{};
UEvent m_uevent{}; UEvent m_uevent{};
}; };
struct ThreadQueueEntry { struct ThreadQueueEntry {
std::string url; Api api;
std::string file;
std::string post;
DownloadCallback callback;
ProgressCallback pcallback;
bool m_delete{}; bool m_delete{};
}; };
@@ -160,22 +156,18 @@ struct ThreadQueue {
threadClose(&m_thread); threadClose(&m_thread);
} }
auto Add(DownloadPriority prio, DownloadCallback callback, ProgressCallback pcallback, std::string url, std::string file, std::string post) -> bool { auto Add(const Api& api) -> bool {
mutexLock(&m_mutex); mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex)); ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
ThreadQueueEntry entry{}; ThreadQueueEntry entry{};
entry.url = url; entry.api = api;
entry.file = file;
entry.post = post;
entry.callback = callback;
entry.pcallback = pcallback;
switch (prio) { switch (api.m_prio) {
case DownloadPriority::Normal: case Priority::Normal:
m_entries.emplace_back(entry); m_entries.emplace_back(entry);
break; break;
case DownloadPriority::High: case Priority::High:
m_entries.emplace_front(entry); m_entries.emplace_front(entry);
break; break;
} }
@@ -216,7 +208,7 @@ auto ProgressCallbackFunc2(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
} }
// log_write("pcall called %u %u %u %u\n", dltotal, dlnow, ultotal, ulnow); // log_write("pcall called %u %u %u %u\n", dltotal, dlnow, ultotal, ulnow);
auto callback = *static_cast<ProgressCallback*>(clientp); auto callback = *static_cast<OnProgress*>(clientp);
if (!callback(dltotal, dlnow, ultotal, ulnow)) { if (!callback(dltotal, dlnow, ultotal, ulnow)) {
return 1; return 1;
} }
@@ -283,36 +275,36 @@ auto WriteFileCallback(void *contents, size_t size, size_t num_files, void *user
return realsize; return realsize;
} }
auto DownloadInternal(CURL* curl, DataStruct& chunk, ProgressCallback pcallback, const std::string& url, const std::string& file, const std::string& post) -> bool { auto DownloadInternal(CURL* curl, DataStruct& chunk, const Api& e) -> DownloadResult {
fs::FsPath safe_buf; fs::FsPath safe_buf;
fs::FsPath tmp_buf; fs::FsPath tmp_buf;
const bool has_file = !file.empty() && file != ""; const bool has_file = !e.m_path.empty() && e.m_path != "";
const bool has_post = !post.empty() && post != ""; const bool has_post = !e.m_fields.m_str.empty() && e.m_fields.m_str != "";
ON_SCOPE_EXIT(if (has_file) { fsFsClose(&chunk.fs); } ); ON_SCOPE_EXIT(if (has_file) { fsFsClose(&chunk.fs); } );
if (has_file) { if (has_file) {
std::strcpy(safe_buf, file.c_str()); std::strcpy(safe_buf, e.m_path);
GetDownloadTempPath(tmp_buf); GetDownloadTempPath(tmp_buf);
R_TRY_RESULT(fsOpenSdCardFileSystem(&chunk.fs), false); R_TRY_RESULT(fsOpenSdCardFileSystem(&chunk.fs), {});
fs::CreateDirectoryRecursivelyWithPath(&chunk.fs, tmp_buf); fs::CreateDirectoryRecursivelyWithPath(&chunk.fs, tmp_buf);
if (auto rc = fsFsCreateFile(&chunk.fs, tmp_buf, 0, 0); R_FAILED(rc) && rc != FsError_PathAlreadyExists) { if (auto rc = fsFsCreateFile(&chunk.fs, tmp_buf, 0, 0); R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
log_write("failed to create file: %s\n", tmp_buf); log_write("failed to create file: %s\n", tmp_buf);
return false; return {};
} }
if (R_FAILED(fsFsOpenFile(&chunk.fs, tmp_buf, FsOpenMode_Write|FsOpenMode_Append, &chunk.f))) { if (R_FAILED(fsFsOpenFile(&chunk.fs, tmp_buf, FsOpenMode_Write|FsOpenMode_Append, &chunk.f))) {
log_write("failed to open file: %s\n", tmp_buf); log_write("failed to open file: %s\n", tmp_buf);
return false; return {};
} }
} }
// reserve the first chunk // reserve the first chunk
chunk.data.reserve(CHUNK_SIZE); chunk.data.reserve(CHUNK_SIZE);
CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, url.c_str()); CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, e.m_url.m_str.c_str());
CURL_EASY_SETOPT_LOG(curl, CURLOPT_USERAGENT, "TotalJustice"); CURL_EASY_SETOPT_LOG(curl, CURLOPT_USERAGENT, "TotalJustice");
CURL_EASY_SETOPT_LOG(curl, CURLOPT_FOLLOWLOCATION, 1L); CURL_EASY_SETOPT_LOG(curl, CURLOPT_FOLLOWLOCATION, 1L);
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYPEER, 0L); CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYPEER, 0L);
@@ -322,13 +314,36 @@ auto DownloadInternal(CURL* curl, DataStruct& chunk, ProgressCallback pcallback,
CURL_EASY_SETOPT_LOG(curl, CURLOPT_BUFFERSIZE, 1024*512); CURL_EASY_SETOPT_LOG(curl, CURLOPT_BUFFERSIZE, 1024*512);
if (has_post) { if (has_post) {
CURL_EASY_SETOPT_LOG(curl, CURLOPT_POSTFIELDS, post.c_str()); CURL_EASY_SETOPT_LOG(curl, CURLOPT_POSTFIELDS, e.m_fields.m_str.c_str());
log_write("setting post field: %s\n", post.c_str()); log_write("setting post field: %s\n", e.m_fields.m_str.c_str());
}
struct curl_slist* list = NULL;
ON_SCOPE_EXIT(if (list) { curl_slist_free_all(list); } );
for (auto& [key, value] : e.m_header) {
// append value (if any).
auto header_str = key;
if (value.empty()) {
header_str += ":";
} else {
header_str += ": " + value;
}
// try to append header chunk.
auto temp = curl_slist_append(list, header_str.c_str());
if (temp) {
list = temp;
}
}
if (list) {
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HTTPHEADER, list);
} }
// progress calls. // progress calls.
if (pcallback) { if (e.m_on_progress) {
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &pcallback); CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &e.m_on_progress);
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc2); CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc2);
} else { } else {
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc1); CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc1);
@@ -343,6 +358,9 @@ auto DownloadInternal(CURL* curl, DataStruct& chunk, ProgressCallback pcallback,
const auto res = curl_easy_perform(curl); const auto res = curl_easy_perform(curl);
bool success = res == CURLE_OK; bool success = res == CURLE_OK;
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (has_file) { if (has_file) {
if (res == CURLE_OK && chunk.offset) { if (res == CURLE_OK && chunk.offset) {
fsFileWrite(&chunk.f, chunk.file_offset, chunk.data.data(), chunk.offset, FsWriteOption_None); fsFileWrite(&chunk.f, chunk.file_offset, chunk.data.data(), chunk.offset, FsWriteOption_None);
@@ -365,18 +383,18 @@ auto DownloadInternal(CURL* curl, DataStruct& chunk, ProgressCallback pcallback,
} }
} }
log_write("Downloaded %s %s\n", url.c_str(), curl_easy_strerror(res)); log_write("Downloaded %s %s\n", e.m_url.m_str.c_str(), curl_easy_strerror(res));
return success; return {success, http_code};
} }
auto DownloadInternal(DataStruct& chunk, ProgressCallback pcallback, const std::string& url, const std::string& file, const std::string& post) -> bool { auto DownloadInternal(DataStruct& chunk, const Api& e) -> DownloadResult {
auto curl = curl_easy_init(); auto curl = curl_easy_init();
if (!curl) { if (!curl) {
log_write("curl init failed\n"); log_write("curl init failed\n");
return false; return {};
} }
ON_SCOPE_EXIT(curl_easy_cleanup(curl)); ON_SCOPE_EXIT(curl_easy_cleanup(curl));
return DownloadInternal(curl, chunk, pcallback, url, file, post); return DownloadInternal(curl, chunk, e);
} }
void DownloadThread(void* p) { void DownloadThread(void* p) {
@@ -393,9 +411,9 @@ void DownloadThread(void* p) {
DataStruct chunk; DataStruct chunk;
#if 1 #if 1
const auto result = DownloadInternal(data->m_curl, chunk, data->m_pcallback, data->m_url, data->m_file, data->m_post); const auto [result, code] = DownloadInternal(data->m_curl, chunk, data->m_api);
if (g_running) { if (g_running) {
DownloadEventData event_data{data->m_callback, std::move(chunk.data), result}; DownloadEventData event_data{data->m_api.m_on_complete, std::move(chunk.data), code, result};
evman::push(std::move(event_data), false); evman::push(std::move(event_data), false);
} else { } else {
break; break;
@@ -444,7 +462,7 @@ void DownloadThreadQueue(void* p) {
} }
if (!thread.InProgress()) { if (!thread.InProgress()) {
thread.Setup(entry.callback, entry.pcallback, entry.url, entry.file, entry.post); thread.Setup(entry.api);
// log_write("[dl queue] starting download\n"); // log_write("[dl queue] starting download\n");
// mark entry for deletion // mark entry for deletion
entry.m_delete = true; entry.m_delete = true;
@@ -467,13 +485,6 @@ void DownloadThreadQueue(void* p) {
for (u32 i = 0; i < pop_count; i++) { for (u32 i = 0; i < pop_count; i++) {
data->m_entries.pop_front(); data->m_entries.pop_front();
} }
// if (delete_any) {
// data->m_entries.clear();
// data->m_entries.
// data->m_entries.erase(std::remove_if(data->m_entries.begin(), data->m_entries.end(), [](auto& a) {
// return a.m_delete;
// }));
// }
} }
log_write("exited download thread queue\n"); log_write("exited download thread queue\n");
@@ -489,7 +500,7 @@ void my_unlock(CURL *handle, curl_lock_data data, void *useptr) {
} // namespace } // namespace
auto DownloadInit() -> bool { auto Init() -> bool {
if (CURLE_OK != curl_global_init(CURL_GLOBAL_DEFAULT)) { if (CURLE_OK != curl_global_init(CURL_GLOBAL_DEFAULT)) {
return false; return false;
} }
@@ -521,7 +532,7 @@ auto DownloadInit() -> bool {
return true; return true;
} }
void DownloadExit() { void Exit() {
g_running = false; g_running = false;
g_thread_queue.Close(); g_thread_queue.Close();
@@ -538,30 +549,38 @@ void DownloadExit() {
curl_global_cleanup(); curl_global_cleanup();
} }
auto DownloadMemory(const std::string& url, const std::string& post, ProgressCallback pcallback) -> std::vector<u8> { auto ToMemory(const Api& e) -> std::vector<u8> {
if (g_url_cache.AddToCache(url)) { if (!e.m_path.empty()) {
return {};
}
if (g_url_cache.AddToCache(e.m_url)) {
DataStruct chunk{}; DataStruct chunk{};
if (DownloadInternal(chunk, pcallback, url, "", post)) { if (DownloadInternal(chunk, e).first) {
return chunk.data; return chunk.data;
} }
} }
return {}; return {};
} }
auto DownloadFile(const std::string& url, const std::string& out, const std::string& post, ProgressCallback pcallback) -> bool { auto ToFile(const Api& e) -> bool {
if (g_url_cache.AddToCache(url)) { if (e.m_path.empty()) {
return false;
}
if (g_url_cache.AddToCache(e.m_url)) {
DataStruct chunk{}; DataStruct chunk{};
if (DownloadInternal(chunk, pcallback, url, out, post)) { if (DownloadInternal(chunk, e).first) {
return true; return true;
} }
} }
return false; return false;
} }
auto DownloadMemoryAsync(const std::string& url, const std::string& post, DownloadCallback callback, ProgressCallback pcallback, DownloadPriority prio) -> bool { auto ToMemoryAsync(const Api& api) -> bool {
#if USE_THREAD_QUEUE #if USE_THREAD_QUEUE
if (g_url_cache.AddToCache(url)) { if (g_url_cache.AddToCache(api.m_url)) {
return g_thread_queue.Add(prio, callback, pcallback, url, "", post); return g_thread_queue.Add(api);
} else { } else {
return false; return false;
} }
@@ -580,10 +599,10 @@ auto DownloadMemoryAsync(const std::string& url, const std::string& post, Downlo
#endif #endif
} }
auto DownloadFileAsync(const std::string& url, const std::string& out, const std::string& post, DownloadCallback callback, ProgressCallback pcallback, DownloadPriority prio) -> bool { auto ToFileAsync(const Api& e) -> bool {
#if USE_THREAD_QUEUE #if USE_THREAD_QUEUE
if (g_url_cache.AddToCache(url)) { if (g_url_cache.AddToCache(e.m_url)) {
return g_thread_queue.Add(prio, callback, pcallback, url, out, post); return g_thread_queue.Add(e);
} else { } else {
return false; return false;
} }
@@ -602,9 +621,9 @@ auto DownloadFileAsync(const std::string& url, const std::string& out, const std
#endif #endif
} }
void DownloadClearCache(const std::string& url) { void ClearCache(const Url& url) {
g_url_cache.AddToCache(url); g_url_cache.AddToCache(url);
g_url_cache.RemoveFromCache(url); g_url_cache.RemoveFromCache(url);
} }
} // namespace sphaira } // namespace sphaira::curl

View File

@@ -398,17 +398,13 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
log_write("starting download\n"); log_write("starting download\n");
const auto url = BuildZipUrl(entry); const auto url = BuildZipUrl(entry);
if (!DownloadFile(url, zip_out, "", [pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){ if (!curl::Api().ToFile(
if (pbox->ShouldExit()) { curl::Url{url},
return false; curl::Path{zip_out},
} curl::OnProgress{pbox->OnDownloadProgressCallback()}
pbox->UpdateTransfer(dlnow, dltotal); )) {
return true;
})) {
log_write("error with download\n"); log_write("error with download\n");
// push popup error box
return false; return false;
// return appletEnterFatalSection();
} }
} }
@@ -682,36 +678,29 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
const auto post = "name=" "switch_user" "&package=" + m_entry.name + "&message=" + out; const auto post = "name=" "switch_user" "&package=" + m_entry.name + "&message=" + out;
const auto file = BuildFeedbackCachePath(m_entry); const auto file = BuildFeedbackCachePath(m_entry);
DownloadFileAsync(URL_POST_FEEDBACK, file, post, [](std::vector<u8>& data, bool success){ curl::Api().ToAsync(
if (success) { curl::Url{URL_POST_FEEDBACK},
log_write("got feedback!\n"); curl::Path{file},
} else { curl::Fields{post},
log_write("failed to send feedback :("); curl::OnComplete{[](std::vector<u8>& data, bool success, long code){
if (success) {
log_write("got feedback!\n");
} else {
log_write("failed to send feedback :(");
}
} }
}); });
} }
}, true)); }, true));
App::Push(sidebar); App::Push(sidebar);
}}), }}),
// std::make_pair(Button::A, Action{m_entry.status == EntryStatus::Update ? "Update" : "Install", [this](){
// App::Push(std::make_shared<ProgressBox>("App Install", [this](auto pbox){
// InstallApp(pbox, m_entry);
// }, 2));
// }}),
std::make_pair(Button::B, Action{"Back"_i18n, [this](){ std::make_pair(Button::B, Action{"Back"_i18n, [this](){
SetPop(); SetPop();
}}) }})
); );
// SidebarEntryCallback
// if (!m_entries_current.empty() && !GetEntry().url.empty()) {
// options->Add(std::make_shared<SidebarEntryCallback>("Show Release Page"))
// }
SetTitleSubHeading("by " + m_entry.author); SetTitleSubHeading("by " + m_entry.author);
// const char* very_long = "total@fedora:~/dev/switch/sphaira$ nxlink build/MinSizeRel/*.nro total@fedora:~/dev/switch/sphaira$ nxlink build/MinSizeRel/*.nro total@fedora:~/dev/switch/sphaira$ nxlink build/MinSizeRel/*.nro total@fedora:~/dev/switch/sphaira$ nxlink build/MinSizeRel/*.nro";
m_details = std::make_shared<ScrollableText>(m_entry.details, 0, 374, 250, 768, 18); m_details = std::make_shared<ScrollableText>(m_entry.details, 0, 374, 250, 768, 18);
m_changelog = std::make_shared<ScrollableText>(m_entry.changelog, 0, 374, 250, 768, 18); m_changelog = std::make_shared<ScrollableText>(m_entry.changelog, 0, 374, 250, 768, 18);
@@ -727,31 +716,18 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
// race condition if we pop the widget before the download completes // race condition if we pop the widget before the download completes
if (!m_banner.image) { if (!m_banner.image) {
DownloadFileAsync(url, path, "", [this, path](std::vector<u8>& data, bool success){ curl::Api().ToFileAsync(
if (success) { curl::Url{url},
EntryLoadImageFile(path, m_banner); curl::Path{path},
curl::Priority::High,
curl::OnComplete{[this, path](std::vector<u8>& data, bool success, long code){
if (success) {
EntryLoadImageFile(path, m_banner);
}
} }
}, nullptr, DownloadPriority::High); });
} }
// ignore screen shots, most apps don't have any sadly.
#if 0
m_screens.resize(m_entry.screens);
for (u32 i = 0; i < m_screens.size(); i++) {
path = BuildScreensCachePath(m_entry.name, i);
url = BuildScreensUrl(m_entry.name, i);
if (fs::file_exists(path.c_str())) {
EntryLoadImageFile(path, m_screens[i]);
} else {
DownloadFileAsync(url.c_str(), path.c_str(), [this, i, path](std::vector<u8>& data, bool success){
EntryLoadImageFile(path, m_screens[i]);
}, nullptr, DownloadPriority::High);
}
}
#endif
SetSubHeading(m_entry.binary); SetSubHeading(m_entry.binary);
SetSubHeading(m_entry.description); SetSubHeading(m_entry.description);
UpdateOptions(); UpdateOptions();
@@ -780,30 +756,17 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
DrawIcon(vg, m_banner, m_entry.image.image ? m_entry.image : m_default_icon, banner_vec, false); DrawIcon(vg, m_banner, m_entry.image.image ? m_entry.image : m_default_icon, banner_vec, false);
DrawIcon(vg, m_entry.image, m_default_icon, icon_vec); DrawIcon(vg, m_entry.image, m_default_icon, icon_vec);
// gfx::drawImage(vg, icon_vec, m_entry.image.image);
constexpr float text_start_x = icon_vec.x;// - 10; constexpr float text_start_x = icon_vec.x;// - 10;
float text_start_y = 218 + line_vec.y; float text_start_y = 218 + line_vec.y;
const float text_inc_y = 32; const float text_inc_y = 32;
const float font_size = 20; const float font_size = 20;
// gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "%s", m_entry.name.c_str());
// gfx::drawTextBox(vg, text_start_x - 20, text_start_y, font_size, icon_vec.w + 20*2, theme->elements[ThemeEntryID_TEXT].colour, m_entry.description.c_str(), NVG_ALIGN_CENTER);
// text_start_y += text_inc_y * 2.0;
// gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "author: %s", m_entry.author.c_str());
// text_start_y += text_inc_y;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "version: %s"_i18n.c_str(), m_entry.version.c_str()); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "version: %s"_i18n.c_str(), m_entry.version.c_str());
text_start_y += text_inc_y; text_start_y += text_inc_y;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "updated: %s"_i18n.c_str(), m_entry.updated.c_str()); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "updated: %s"_i18n.c_str(), m_entry.updated.c_str());
text_start_y += text_inc_y; text_start_y += text_inc_y;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "category: %s"_i18n.c_str(), m_entry.category.c_str()); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "category: %s"_i18n.c_str(), m_entry.category.c_str());
text_start_y += text_inc_y; text_start_y += text_inc_y;
// gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "license: %s", m_entry.license.c_str());
// text_start_y += text_inc_y;
// gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "title: %s", m_entry.title.c_str());
// text_start_y += text_inc_y;
// gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "filesize: %.2f MiB", (double)m_entry.filesize / 1024.0);
// text_start_y += text_inc_y;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "extracted: %.2f MiB"_i18n.c_str(), (double)m_entry.extracted / 1024.0); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "extracted: %.2f MiB"_i18n.c_str(), (double)m_entry.extracted / 1024.0);
text_start_y += text_inc_y; text_start_y += text_inc_y;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "app_dls: %s"_i18n.c_str(), AppDlToStr(m_entry.app_dls).c_str()); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "app_dls: %s"_i18n.c_str(), AppDlToStr(m_entry.app_dls).c_str());
@@ -1058,20 +1021,6 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
); );
m_repo_download_state = ImageDownloadState::Progress; m_repo_download_state = ImageDownloadState::Progress;
#if 0
DownloadMemoryAsync(URL_JSON, [this](std::vector<u8>& data, bool success){
if (success) {
repo_json = data;
repo_json.push_back('\0');
m_repo_download_state = ImageDownloadState::Done;
if (HasFocus()) {
ScanHomebrew();
}
} else {
m_repo_download_state = ImageDownloadState::Failed;
}
});
#else
FsTimeStampRaw time_stamp{}; FsTimeStampRaw time_stamp{};
u64 current_time{}; u64 current_time{};
bool download_file = false; bool download_file = false;
@@ -1102,20 +1051,23 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
// download_file = true; // download_file = true;
if (download_file) { if (download_file) {
DownloadFileAsync(URL_JSON, REPO_PATH, "", [this](std::vector<u8>& data, bool success){ curl::Api().ToFileAsync(
if (success) { curl::Url{URL_JSON},
m_repo_download_state = ImageDownloadState::Done; curl::Path{REPO_PATH},
if (HasFocus()) { curl::OnComplete{[this](std::vector<u8>& data, bool success, long code){
ScanHomebrew(); if (success) {
m_repo_download_state = ImageDownloadState::Done;
if (HasFocus()) {
ScanHomebrew();
}
} else {
m_repo_download_state = ImageDownloadState::Failed;
} }
} else {
m_repo_download_state = ImageDownloadState::Failed;
} }
}); });
} else { } else {
m_repo_download_state = ImageDownloadState::Done; m_repo_download_state = ImageDownloadState::Done;
} }
#endif
m_filter = (Filter)ini_getl(INI_SECTION, "filter", m_filter, App::CONFIG_PATH); m_filter = (Filter)ini_getl(INI_SECTION, "filter", m_filter, App::CONFIG_PATH);
m_sort = (SortType)ini_getl(INI_SECTION, "sort", m_sort, App::CONFIG_PATH); m_sort = (SortType)ini_getl(INI_SECTION, "sort", m_sort, App::CONFIG_PATH);
@@ -1174,14 +1126,19 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} else { } else {
const auto url = BuildIconUrl(e); const auto url = BuildIconUrl(e);
e.image.state = ImageDownloadState::Progress; e.image.state = ImageDownloadState::Progress;
DownloadFileAsync(url, path, "", [this, index](std::vector<u8>& data, bool success) { curl::Api().ToFileAsync(
if (success) { curl::Url{url},
m_entries[index].image.state = ImageDownloadState::Done; curl::Path{path},
} else { curl::Priority::High,
m_entries[index].image.state = ImageDownloadState::Failed; curl::OnComplete{[this, index](std::vector<u8>& data, bool success, long code) {
log_write("failed to download image\n"); if (success) {
m_entries[index].image.state = ImageDownloadState::Done;
} else {
m_entries[index].image.state = ImageDownloadState::Failed;
log_write("failed to download image\n");
}
} }
}, nullptr, DownloadPriority::High); });
} }
} break; } break;
case ImageDownloadState::Progress: { case ImageDownloadState::Progress: {

View File

@@ -232,15 +232,12 @@ auto GetRomIcon(ProgressBox* pbox, std::string filename, std::string extension,
// try and download icon // try and download icon
if (!pbox->ShouldExit()) { if (!pbox->ShouldExit()) {
// auto png_image = DownloadMemory(ra_thumbnail_url.c_str());
pbox->NewTransfer("Downloading "_i18n + gh_thumbnail_url); pbox->NewTransfer("Downloading "_i18n + gh_thumbnail_url);
auto png_image = DownloadMemory(gh_thumbnail_url, "", [pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){ auto png_image = curl::Api().ToMemory(
if (pbox->ShouldExit()) { curl::Url{gh_thumbnail_url},
return false; curl::OnProgress{pbox->OnDownloadProgressCallback()}
} );
pbox->UpdateTransfer(dlnow, dltotal);
return true;
});
if (!png_image.empty()) { if (!png_image.empty()) {
return png_image; return png_image;
} }

View File

@@ -67,34 +67,30 @@ void from_json(const std::vector<u8>& data, GhApiEntry& e) {
); );
} }
auto DownloadApp(ProgressBox* pbox, const GhApiAsset* gh_asset, const AssetEntry& entry = {}) -> bool { auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry& entry = {}) -> bool {
static const fs::FsPath temp_file{"/switch/sphaira/cache/ghdl.temp"};
constexpr auto chunk_size = 1024 * 512; // 512KiB constexpr auto chunk_size = 1024 * 512; // 512KiB
fs::FsNativeSd fs; fs::FsNativeSd fs;
R_TRY_RESULT(fs.GetFsOpenResult(), false); R_TRY_RESULT(fs.GetFsOpenResult(), false);
if (!gh_asset || gh_asset->browser_download_url.empty()) { if (gh_asset.browser_download_url.empty()) {
log_write("failed to find asset\n"); log_write("failed to find asset\n");
return false; return false;
} }
static fs::FsPath temp_file{"/switch/sphaira/cache/ghdl.temp"};
// 2. download the asset // 2. download the asset
if (!pbox->ShouldExit()) { if (!pbox->ShouldExit()) {
pbox->NewTransfer("Downloading "_i18n + gh_asset->name); pbox->NewTransfer("Downloading "_i18n + gh_asset.name);
log_write("starting download: %s\n", gh_asset->browser_download_url.c_str()); log_write("starting download: %s\n", gh_asset.browser_download_url.c_str());
DownloadClearCache(gh_asset->browser_download_url); curl::ClearCache(gh_asset.browser_download_url);
if (!DownloadFile(gh_asset->browser_download_url, temp_file, "", [pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){ if (!curl::Api().ToFile(
if (pbox->ShouldExit()) { curl::Url{gh_asset.browser_download_url},
return false; curl::Path{temp_file},
} curl::OnProgress{pbox->OnDownloadProgressCallback()}
pbox->UpdateTransfer(dlnow, dltotal); )){
return true;
})) {
log_write("error with download\n"); log_write("error with download\n");
// push popup error box
return false; return false;
} }
} }
@@ -107,7 +103,7 @@ auto DownloadApp(ProgressBox* pbox, const GhApiAsset* gh_asset, const AssetEntry
} }
// 3. extract the zip / file // 3. extract the zip / file
if (gh_asset->content_type == "application/zip") { if (gh_asset.content_type == "application/zip") {
log_write("found zip\n"); log_write("found zip\n");
auto zfile = unzOpen64(temp_file); auto zfile = unzOpen64(temp_file);
if (!zfile) { if (!zfile) {
@@ -207,14 +203,11 @@ auto DownloadAssets(ProgressBox* pbox, const std::string& url, GhApiEntry& out)
pbox->NewTransfer("Downloading json"_i18n); pbox->NewTransfer("Downloading json"_i18n);
log_write("starting download\n"); log_write("starting download\n");
DownloadClearCache(url); curl::ClearCache(url);
const auto json = DownloadMemory(url, "", [pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){ const auto json = curl::Api().ToMemory(
if (pbox->ShouldExit()) { curl::Url{url},
return false; curl::OnProgress{pbox->OnDownloadProgressCallback()}
} );
pbox->UpdateTransfer(dlnow, dltotal);
return true;
});
if (json.empty()) { if (json.empty()) {
log_write("error with download\n"); log_write("error with download\n");
@@ -241,19 +234,18 @@ auto DownloadApp(ProgressBox* pbox, const std::string& url, const AssetEntry& en
return false; return false;
} }
GhApiAsset* gh_asset{}; const auto it = std::find_if(
for (auto& p : gh_entry.assets) { gh_entry.assets.cbegin(), gh_entry.assets.cend(), [&entry](auto& e) {
if (p.name == entry.name) { return entry.name == e.name;
gh_asset = &p;
} }
} );
if (!gh_asset || gh_asset->browser_download_url.empty()) { if (it == gh_entry.assets.cend() || it->browser_download_url.empty()) {
log_write("failed to find asset\n"); log_write("failed to find asset\n");
return false; return false;
} }
return DownloadApp(pbox, gh_asset, entry); return DownloadApp(pbox, *it, entry);
} }
} // namespace } // namespace
@@ -313,7 +305,7 @@ Menu::Menu() : MenuBase{"GitHub"_i18n} {
const auto index = *op_index; const auto index = *op_index;
const auto& asset_entry = gh_entry.assets[index]; const auto& asset_entry = gh_entry.assets[index];
App::Push(std::make_shared<ProgressBox>("Downloading "_i18n + GetEntry().name, [this, &asset_entry](auto pbox){ App::Push(std::make_shared<ProgressBox>("Downloading "_i18n + GetEntry().name, [this, &asset_entry](auto pbox){
return DownloadApp(pbox, &asset_entry); return DownloadApp(pbox, asset_entry);
}, [this](bool success){ }, [this](bool success){
if (success) { if (success) {
App::Notify("Downloaded " + GetEntry().name); App::Notify("Downloaded " + GetEntry().name);

View File

@@ -35,16 +35,13 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
pbox->NewTransfer("Downloading "_i18n + version); pbox->NewTransfer("Downloading "_i18n + version);
log_write("starting download: %s\n", url.c_str()); log_write("starting download: %s\n", url.c_str());
DownloadClearCache(url); curl::ClearCache(url);
if (!DownloadFile(url, zip_out, "", [pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){ if (!curl::Api().ToFile(
if (pbox->ShouldExit()) { curl::Url{url},
return false; curl::Path{zip_out},
} curl::OnProgress{pbox->OnDownloadProgressCallback()}
pbox->UpdateTransfer(dlnow, dltotal); )) {
return true;
})) {
log_write("error with download\n"); log_write("error with download\n");
// push popup error box
return false; return false;
} }
} }
@@ -143,58 +140,61 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
} // namespace } // namespace
MainMenu::MainMenu() { MainMenu::MainMenu() {
DownloadMemoryAsync("https://api.github.com/repos/ITotalJustice/sphaira/releases/latest", "", [this](std::vector<u8>& data, bool success){ curl::Api().ToMemoryAsync(
m_update_state = UpdateState::Error; curl::Url{"https://api.github.com/repos/ITotalJustice/sphaira/releases/latest"},
ON_SCOPE_EXIT( log_write("update status: %u\n", (u8)m_update_state) ); curl::OnComplete{[this](std::vector<u8>& data, bool success, long code){
m_update_state = UpdateState::Error;
ON_SCOPE_EXIT( log_write("update status: %u\n", (u8)m_update_state) );
if (!success) { if (!success) {
return false; return false;
} }
auto json = yyjson_read((const char*)data.data(), data.size(), 0); auto json = yyjson_read((const char*)data.data(), data.size(), 0);
R_UNLESS(json, false); R_UNLESS(json, false);
ON_SCOPE_EXIT(yyjson_doc_free(json)); ON_SCOPE_EXIT(yyjson_doc_free(json));
auto root = yyjson_doc_get_root(json); auto root = yyjson_doc_get_root(json);
R_UNLESS(root, false); R_UNLESS(root, false);
auto tag_key = yyjson_obj_get(root, "tag_name"); auto tag_key = yyjson_obj_get(root, "tag_name");
R_UNLESS(tag_key, false); R_UNLESS(tag_key, false);
const auto version = yyjson_get_str(tag_key);
R_UNLESS(version, false);
if (std::strcmp(APP_VERSION, version) >= 0) {
m_update_state = UpdateState::None;
return true;
}
auto body_key = yyjson_obj_get(root, "body");
R_UNLESS(body_key, false);
const auto body = yyjson_get_str(body_key);
R_UNLESS(body, false);
auto assets = yyjson_obj_get(root, "assets");
R_UNLESS(assets, false);
auto idx0 = yyjson_arr_get(assets, 0);
R_UNLESS(idx0, false);
auto url_key = yyjson_obj_get(idx0, "browser_download_url");
R_UNLESS(url_key, false);
const auto url = yyjson_get_str(url_key);
R_UNLESS(url, false);
m_update_version = version;
m_update_url = url;
m_update_description = body;
m_update_state = UpdateState::Update;
log_write("found url: %s\n", url);
log_write("found body: %s\n", body);
App::Notify("Update avaliable: "_i18n + m_update_version);
const auto version = yyjson_get_str(tag_key);
R_UNLESS(version, false);
if (std::strcmp(APP_VERSION, version) >= 0) {
m_update_state = UpdateState::None;
return true; return true;
} }
auto body_key = yyjson_obj_get(root, "body");
R_UNLESS(body_key, false);
const auto body = yyjson_get_str(body_key);
R_UNLESS(body, false);
auto assets = yyjson_obj_get(root, "assets");
R_UNLESS(assets, false);
auto idx0 = yyjson_arr_get(assets, 0);
R_UNLESS(idx0, false);
auto url_key = yyjson_obj_get(idx0, "browser_download_url");
R_UNLESS(url_key, false);
const auto url = yyjson_get_str(url_key);
R_UNLESS(url, false);
m_update_version = version;
m_update_url = url;
m_update_description = body;
m_update_state = UpdateState::Update;
log_write("found url: %s\n", url);
log_write("found body: %s\n", body);
App::Notify("Update avaliable: "_i18n + m_update_version);
return true;
}); });
AddOnLPress(); AddOnLPress();

View File

@@ -272,7 +272,7 @@ void from_json(const std::vector<u8>& data, PackList& e) {
} }
auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool { auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
static fs::FsPath zip_out{"/switch/sphaira/cache/themezer/temp.zip"}; static const fs::FsPath zip_out{"/switch/sphaira/cache/themezer/temp.zip"};
constexpr auto chunk_size = 1024 * 512; // 512KiB constexpr auto chunk_size = 1024 * 512; // 512KiB
fs::FsNativeSd fs; fs::FsNativeSd fs;
@@ -287,18 +287,15 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
const auto url = apiBuildUrlDownloadPack(entry); const auto url = apiBuildUrlDownloadPack(entry);
log_write("using url: %s\n", url.c_str()); log_write("using url: %s\n", url.c_str());
DownloadClearCache(url); curl::ClearCache(url);
const auto data = DownloadMemory(url, "", [pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){ const auto data = curl::Api().ToMemory(
if (pbox->ShouldExit()) { curl::Url{url},
return false; curl::OnProgress{pbox->OnDownloadProgressCallback()}
} );
pbox->UpdateTransfer(dlnow, dltotal);
return true;
});
if (data.empty()) { if (data.empty()) {
log_write("error with download: %s\n", url.c_str()); log_write("error with download: %s\n", url.c_str());
// push popup error box
return false; return false;
} }
@@ -310,16 +307,19 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
pbox->NewTransfer("Downloading "_i18n + entry.details.name); pbox->NewTransfer("Downloading "_i18n + entry.details.name);
log_write("starting download: %s\n", download_pack.url.c_str()); log_write("starting download: %s\n", download_pack.url.c_str());
DownloadClearCache(download_pack.url); curl::ClearCache(download_pack.url);
if (!DownloadFile(download_pack.url, zip_out, "", [pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){ if (!curl::Api().ToFile(
if (pbox->ShouldExit()) { curl::Url{download_pack.url},
return false; curl::Path{zip_out},
curl::OnProgress{[pbox](u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow){
if (pbox->ShouldExit()) {
return false;
}
pbox->UpdateTransfer(dlnow, dltotal);
return true;
} }
pbox->UpdateTransfer(dlnow, dltotal);
return true;
})) { })) {
log_write("error with download\n"); log_write("error with download\n");
// push popup error box
return false; return false;
} }
} }
@@ -638,15 +638,20 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
const auto url = theme.preview.thumb; const auto url = theme.preview.thumb;
log_write("downloading url: %s\n", url.c_str()); log_write("downloading url: %s\n", url.c_str());
image.state = ImageDownloadState::Progress; image.state = ImageDownloadState::Progress;
DownloadFileAsync(url, path, "", [this, index, &image](std::vector<u8>& data, bool success) { curl::Api().ToFileAsync(
if (success) { curl::Url{url},
image.state = ImageDownloadState::Done; curl::Path{path},
log_write("downloaded themezer image\n"); curl::Priority::High,
} else { curl::OnComplete{[this, index, &image](std::vector<u8>& data, bool success, long code) {
image.state = ImageDownloadState::Failed; if (success) {
log_write("failed to download image\n"); image.state = ImageDownloadState::Done;
log_write("downloaded themezer image\n");
} else {
image.state = ImageDownloadState::Failed;
log_write("failed to download image\n");
}
} }
}, nullptr, DownloadPriority::High); });
} }
} break; } break;
case ImageDownloadState::Progress: { case ImageDownloadState::Progress: {
@@ -711,34 +716,38 @@ void Menu::PackListDownload() {
log_write("\npackList_url: %s\n\n", packList_url.c_str()); log_write("\npackList_url: %s\n\n", packList_url.c_str());
log_write("\nthemeList_url: %s\n\n", themeList_url.c_str()); log_write("\nthemeList_url: %s\n\n", themeList_url.c_str());
DownloadClearCache(packList_url); curl::ClearCache(packList_url);
DownloadMemoryAsync(packList_url, "", [this, page_index](std::vector<u8>& data, bool success){ curl::Api().ToMemoryAsync(
log_write("got themezer data\n"); curl::Url{packList_url},
if (!success) { curl::Priority::High,
curl::OnComplete{[this, page_index](std::vector<u8>& data, bool success, long code){
log_write("got themezer data\n");
if (!success) {
auto& page = m_pages[page_index-1];
page.m_ready = PageLoadState::Error;
log_write("failed to get themezer data...\n");
return;
}
PackList a;
from_json(data, a);
m_pages.resize(a.pagination.page_count);
auto& page = m_pages[page_index-1]; auto& page = m_pages[page_index-1];
page.m_ready = PageLoadState::Error;
log_write("failed to get themezer data...\n"); page.m_packList = a.packList;
return; page.m_pagination = a.pagination;
page.m_ready = PageLoadState::Done;
m_page_index_max = a.pagination.page_count;
char subheading[128];
std::snprintf(subheading, sizeof(subheading), "Page %zu / %zu"_i18n.c_str(), m_page_index+1, m_page_index_max);
SetSubHeading(subheading);
log_write("a.pagination.page: %u\n", a.pagination.page);
log_write("a.pagination.page_count: %u\n", a.pagination.page_count);
} }
});
PackList a;
from_json(data, a);
m_pages.resize(a.pagination.page_count);
auto& page = m_pages[page_index-1];
page.m_packList = a.packList;
page.m_pagination = a.pagination;
page.m_ready = PageLoadState::Done;
m_page_index_max = a.pagination.page_count;
char subheading[128];
std::snprintf(subheading, sizeof(subheading), "Page %zu / %zu"_i18n.c_str(), m_page_index+1, m_page_index_max);
SetSubHeading(subheading);
log_write("a.pagination.page: %u\n", a.pagination.page);
log_write("a.pagination.page_count: %u\n", a.pagination.page_count);
}, nullptr, DownloadPriority::High);
} }
} // namespace sphaira::ui::menu::themezer } // namespace sphaira::ui::menu::themezer