mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-27 01:45:35 -05:00
fix resources not being downloaded by fallbacking to github api
- also add an overload to AsyncWebRequest::expect that gives you the http status code
This commit is contained in:
parent
8551071ac9
commit
a418828394
4 changed files with 131 additions and 44 deletions
|
@ -56,6 +56,7 @@ namespace geode::utils::web {
|
||||||
|
|
||||||
using AsyncProgress = std::function<void(SentAsyncWebRequest&, double, double)>;
|
using AsyncProgress = std::function<void(SentAsyncWebRequest&, double, double)>;
|
||||||
using AsyncExpect = std::function<void(std::string const&)>;
|
using AsyncExpect = std::function<void(std::string const&)>;
|
||||||
|
using AsyncExpectCode = std::function<void(std::string const&, int)>;
|
||||||
using AsyncThen = std::function<void(SentAsyncWebRequest&, ByteVector const&)>;
|
using AsyncThen = std::function<void(SentAsyncWebRequest&, ByteVector const&)>;
|
||||||
using AsyncCancelled = std::function<void(SentAsyncWebRequest&)>;
|
using AsyncCancelled = std::function<void(SentAsyncWebRequest&)>;
|
||||||
|
|
||||||
|
@ -74,7 +75,7 @@ namespace geode::utils::web {
|
||||||
|
|
||||||
void pause();
|
void pause();
|
||||||
void resume();
|
void resume();
|
||||||
void error(std::string const& error);
|
void error(std::string const& error, int code);
|
||||||
void doCancel();
|
void doCancel();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -112,7 +113,7 @@ namespace geode::utils::web {
|
||||||
std::optional<std::string> m_joinID;
|
std::optional<std::string> m_joinID;
|
||||||
std::string m_url;
|
std::string m_url;
|
||||||
AsyncThen m_then = nullptr;
|
AsyncThen m_then = nullptr;
|
||||||
AsyncExpect m_expect = nullptr;
|
AsyncExpectCode m_expect = nullptr;
|
||||||
AsyncProgress m_progress = nullptr;
|
AsyncProgress m_progress = nullptr;
|
||||||
AsyncCancelled m_cancelled = nullptr;
|
AsyncCancelled m_cancelled = nullptr;
|
||||||
bool m_sent = false;
|
bool m_sent = false;
|
||||||
|
@ -157,25 +158,32 @@ namespace geode::utils::web {
|
||||||
*/
|
*/
|
||||||
AsyncWebResponse fetch(std::string const& url);
|
AsyncWebResponse fetch(std::string const& url);
|
||||||
/**
|
/**
|
||||||
* Specify a callback to run if the download fails. Runs in the GD
|
* Specify a callback to run if the download fails. The callback is
|
||||||
* thread, so interacting with UI is safe
|
* always ran in the GD thread, so interacting with UI is safe
|
||||||
* @param handler Callback to run if the download fails
|
* @param handler Callback to run if the download fails
|
||||||
* @returns Same AsyncWebRequest
|
* @returns Same AsyncWebRequest
|
||||||
*/
|
*/
|
||||||
AsyncWebRequest& expect(AsyncExpect handler);
|
AsyncWebRequest& expect(AsyncExpect handler);
|
||||||
/**
|
/**
|
||||||
* Specify a callback to run when the download progresses. Runs in the
|
* Specify a callback to run if the download fails. The callback is
|
||||||
* GD thread, so interacting with UI is safe
|
* always ran in the GD thread, so interacting with UI is safe
|
||||||
|
* @param handler Callback to run if the download fails
|
||||||
|
* @returns Same AsyncWebRequest
|
||||||
|
*/
|
||||||
|
AsyncWebRequest& expect(AsyncExpectCode handler);
|
||||||
|
/**
|
||||||
|
* Specify a callback to run when the download progresses. The callback is
|
||||||
|
* always ran in the GD thread, so interacting with UI is safe
|
||||||
* @param handler Callback to run when the download progresses
|
* @param handler Callback to run when the download progresses
|
||||||
* @returns Same AsyncWebRequest
|
* @returns Same AsyncWebRequest
|
||||||
*/
|
*/
|
||||||
AsyncWebRequest& progress(AsyncProgress handler);
|
AsyncWebRequest& progress(AsyncProgress handler);
|
||||||
/**
|
/**
|
||||||
* Specify a callback to run if the download is cancelled. Runs in the
|
* Specify a callback to run if the download is cancelled. The callback is
|
||||||
* GD thread, so interacting with UI is safe. Web requests may be
|
* always ran in the GD thread, so interacting with UI is safe. Web
|
||||||
* cancelled after they are finished (for example, if downloading files
|
* requests may be cancelled after they are finished (for example, if
|
||||||
* in bulk and one fails). In that case, handle freeing up the results
|
* downloading files in bulk and one fails). In that case, handle
|
||||||
* of `then` in this handler
|
* freeing up the results of `then` in this handler
|
||||||
* @param handler Callback to run if the download is cancelled
|
* @param handler Callback to run if the download is cancelled
|
||||||
* @returns Same AsyncWebRequest
|
* @returns Same AsyncWebRequest
|
||||||
*/
|
*/
|
||||||
|
@ -294,7 +302,7 @@ namespace geode::utils::web {
|
||||||
handle(conv.unwrap());
|
handle(conv.unwrap());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
req.error("Unable to convert value: " + conv.unwrapErr());
|
req.error("Unable to convert value: " + conv.unwrapErr(), -1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return m_request;
|
return m_request;
|
||||||
|
@ -309,7 +317,7 @@ namespace geode::utils::web {
|
||||||
handle(req, conv.value());
|
handle(req, conv.value());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
req.error("Unable to convert value: " + conv.error());
|
req.error("Unable to convert value: " + conv.error(), -1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return m_request;
|
return m_request;
|
||||||
|
|
|
@ -466,16 +466,35 @@ bool Loader::Impl::platformConsoleOpen() const {
|
||||||
return m_platformConsoleOpen;
|
return m_platformConsoleOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Loader::Impl::downloadLoaderResources() {
|
void Loader::Impl::fetchLatestGithubRelease(
|
||||||
auto version = this->getVersion().toString();
|
std::function<void(nlohmann::json const&)> then,
|
||||||
|
std::function<void(std::string const&)> expect
|
||||||
|
) {
|
||||||
|
if (m_latestGithubRelease) {
|
||||||
|
return then(m_latestGithubRelease.value());
|
||||||
|
}
|
||||||
|
web::AsyncWebRequest()
|
||||||
|
.join("loader-auto-update-check")
|
||||||
|
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest")
|
||||||
|
.json()
|
||||||
|
.then([this, then](nlohmann::json const& json) {
|
||||||
|
m_latestGithubRelease = json;
|
||||||
|
then(json);
|
||||||
|
})
|
||||||
|
.expect(expect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loader::Impl::tryDownloadLoaderResources(
|
||||||
|
std::string const& url,
|
||||||
|
bool tryLatestOnError
|
||||||
|
) {
|
||||||
auto tempResourcesZip = dirs::getTempDir() / "new.zip";
|
auto tempResourcesZip = dirs::getTempDir() / "new.zip";
|
||||||
auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID();
|
auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID();
|
||||||
|
|
||||||
web::AsyncWebRequest()
|
web::AsyncWebRequest()
|
||||||
.join("update-geode-loader-resources")
|
// use the url as a join handle
|
||||||
.fetch(fmt::format(
|
.join(url)
|
||||||
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", version
|
.fetch(url)
|
||||||
))
|
|
||||||
.into(tempResourcesZip)
|
.into(tempResourcesZip)
|
||||||
.then([tempResourcesZip, resourcesDir](auto) {
|
.then([tempResourcesZip, resourcesDir](auto) {
|
||||||
// unzip resources zip
|
// unzip resources zip
|
||||||
|
@ -487,10 +506,17 @@ void Loader::Impl::downloadLoaderResources() {
|
||||||
}
|
}
|
||||||
ResourceDownloadEvent(UpdateFinished()).post();
|
ResourceDownloadEvent(UpdateFinished()).post();
|
||||||
})
|
})
|
||||||
.expect([](std::string const& info) {
|
.expect([this, tryLatestOnError](std::string const& info, int code) {
|
||||||
ResourceDownloadEvent(
|
// if the url was not found, try downloading latest release instead
|
||||||
UpdateFailed("Unable to download resources: " + info)
|
// (for development versions)
|
||||||
).post();
|
if (code == 404 && tryLatestOnError) {
|
||||||
|
this->downloadLoaderResources(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ResourceDownloadEvent(
|
||||||
|
UpdateFailed("Unable to download resources: " + info)
|
||||||
|
).post();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.progress([](auto&, double now, double total) {
|
.progress([](auto&, double now, double total) {
|
||||||
ResourceDownloadEvent(
|
ResourceDownloadEvent(
|
||||||
|
@ -502,6 +528,45 @@ void Loader::Impl::downloadLoaderResources() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Loader::Impl::downloadLoaderResources(bool useLatestRelease) {
|
||||||
|
if (!useLatestRelease) {
|
||||||
|
this->tryDownloadLoaderResources(fmt::format(
|
||||||
|
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip",
|
||||||
|
this->getVersion().toString()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fetchLatestGithubRelease(
|
||||||
|
[this](nlohmann::json const& raw) {
|
||||||
|
auto json = raw;
|
||||||
|
JsonChecker checker(json);
|
||||||
|
auto root = checker.root("[]").obj();
|
||||||
|
|
||||||
|
// find release asset
|
||||||
|
for (auto asset : root.needs("assets").iterate()) {
|
||||||
|
auto obj = asset.obj();
|
||||||
|
if (obj.needs("name").template get<std::string>() == "resources.zip") {
|
||||||
|
this->tryDownloadLoaderResources(
|
||||||
|
obj.needs("browser_download_url").template get<std::string>(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceDownloadEvent(
|
||||||
|
UpdateFailed("Unable to find resources in latest GitHub release")
|
||||||
|
).post();
|
||||||
|
},
|
||||||
|
[this](std::string const& info) {
|
||||||
|
ResourceDownloadEvent(
|
||||||
|
UpdateFailed("Unable to download resources: " + info)
|
||||||
|
).post();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Loader::Impl::verifyLoaderResources() {
|
bool Loader::Impl::verifyLoaderResources() {
|
||||||
static std::optional<bool> CACHED = std::nullopt;
|
static std::optional<bool> CACHED = std::nullopt;
|
||||||
if (CACHED.has_value()) {
|
if (CACHED.has_value()) {
|
||||||
|
@ -587,11 +652,8 @@ void Loader::Impl::downloadLoaderUpdate(std::string const& url) {
|
||||||
|
|
||||||
void Loader::Impl::checkForLoaderUpdates() {
|
void Loader::Impl::checkForLoaderUpdates() {
|
||||||
// Check for updates in the background
|
// Check for updates in the background
|
||||||
web::AsyncWebRequest()
|
fetchLatestGithubRelease(
|
||||||
.join("loader-auto-update-check")
|
[this](nlohmann::json const& raw) {
|
||||||
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest")
|
|
||||||
.json()
|
|
||||||
.then([this](nlohmann::json const& raw) {
|
|
||||||
auto json = raw;
|
auto json = raw;
|
||||||
JsonChecker checker(json);
|
JsonChecker checker(json);
|
||||||
auto root = checker.root("[]").obj();
|
auto root = checker.root("[]").obj();
|
||||||
|
@ -626,12 +688,13 @@ void Loader::Impl::checkForLoaderUpdates() {
|
||||||
LoaderUpdateEvent(
|
LoaderUpdateEvent(
|
||||||
UpdateFailed("Unable to find release asset for " GEODE_PLATFORM_NAME)
|
UpdateFailed("Unable to find release asset for " GEODE_PLATFORM_NAME)
|
||||||
).post();
|
).post();
|
||||||
})
|
},
|
||||||
.expect([](std::string const& info) {
|
[](std::string const& info) {
|
||||||
LoaderUpdateEvent(
|
LoaderUpdateEvent(
|
||||||
UpdateFailed("Unable to check for updates: " + info)
|
UpdateFailed("Unable to check for updates: " + info)
|
||||||
).post();
|
).post();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Loader::Impl::isNewUpdateDownloaded() const {
|
bool Loader::Impl::isNewUpdateDownloaded() const {
|
||||||
|
|
|
@ -59,6 +59,9 @@ namespace geode {
|
||||||
std::vector<ghc::filesystem::path> m_texturePaths;
|
std::vector<ghc::filesystem::path> m_texturePaths;
|
||||||
bool m_isSetup = false;
|
bool m_isSetup = false;
|
||||||
|
|
||||||
|
// cache for the json of the latest github release to avoid hitting
|
||||||
|
// the github api too much
|
||||||
|
std::optional<nlohmann::json> m_latestGithubRelease;
|
||||||
bool m_isNewUpdateDownloaded = false;
|
bool m_isNewUpdateDownloaded = false;
|
||||||
|
|
||||||
std::condition_variable m_earlyLoadFinishedCV;
|
std::condition_variable m_earlyLoadFinishedCV;
|
||||||
|
@ -87,8 +90,13 @@ namespace geode {
|
||||||
Result<tulip::hook::HandlerHandle> getHandler(void* address);
|
Result<tulip::hook::HandlerHandle> getHandler(void* address);
|
||||||
Result<> removeHandler(void* address);
|
Result<> removeHandler(void* address);
|
||||||
|
|
||||||
void downloadLoaderResources();
|
void tryDownloadLoaderResources(std::string const& url, bool tryLatestOnError = true);
|
||||||
|
void downloadLoaderResources(bool useLatestRelease = false);
|
||||||
void downloadLoaderUpdate(std::string const& url);
|
void downloadLoaderUpdate(std::string const& url);
|
||||||
|
void fetchLatestGithubRelease(
|
||||||
|
std::function<void(nlohmann::json const&)> then,
|
||||||
|
std::function<void(std::string const&)> expect
|
||||||
|
);
|
||||||
|
|
||||||
bool loadHooks();
|
bool loadHooks();
|
||||||
void setupIPC();
|
void setupIPC();
|
||||||
|
|
|
@ -140,7 +140,6 @@ Result<std::string> web::fetch(std::string const& url) {
|
||||||
class SentAsyncWebRequest::Impl {
|
class SentAsyncWebRequest::Impl {
|
||||||
private:
|
private:
|
||||||
enum class Status {
|
enum class Status {
|
||||||
|
|
||||||
Paused,
|
Paused,
|
||||||
Running,
|
Running,
|
||||||
Finished,
|
Finished,
|
||||||
|
@ -150,7 +149,7 @@ private:
|
||||||
std::string m_id;
|
std::string m_id;
|
||||||
std::string m_url;
|
std::string m_url;
|
||||||
std::vector<AsyncThen> m_thens;
|
std::vector<AsyncThen> m_thens;
|
||||||
std::vector<AsyncExpect> m_expects;
|
std::vector<AsyncExpectCode> m_expects;
|
||||||
std::vector<AsyncProgress> m_progresses;
|
std::vector<AsyncProgress> m_progresses;
|
||||||
std::vector<AsyncCancelled> m_cancelleds;
|
std::vector<AsyncCancelled> m_cancelleds;
|
||||||
Status m_status = Status::Paused;
|
Status m_status = Status::Paused;
|
||||||
|
@ -173,7 +172,7 @@ private:
|
||||||
|
|
||||||
void pause();
|
void pause();
|
||||||
void resume();
|
void resume();
|
||||||
void error(std::string const& error);
|
void error(std::string const& error, int code);
|
||||||
void doCancel();
|
void doCancel();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -212,7 +211,7 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
||||||
|
|
||||||
auto curl = curl_easy_init();
|
auto curl = curl_easy_init();
|
||||||
if (!curl) {
|
if (!curl) {
|
||||||
return this->error("Curl not initialized");
|
return this->error("Curl not initialized", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// resulting byte array
|
// resulting byte array
|
||||||
|
@ -290,8 +289,10 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
||||||
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &data);
|
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &data);
|
||||||
auto res = curl_easy_perform(curl);
|
auto res = curl_easy_perform(curl);
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
|
long code = 0;
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
return this->error("Fetch failed: " + std::string(curl_easy_strerror(res)));
|
return this->error("Fetch failed: " + std::string(curl_easy_strerror(res)), code);
|
||||||
}
|
}
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
|
@ -335,7 +336,7 @@ void SentAsyncWebRequest::Impl::doCancel() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this->error("Request cancelled");
|
this->error("Request cancelled", -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SentAsyncWebRequest::Impl::cancel() {
|
void SentAsyncWebRequest::Impl::cancel() {
|
||||||
|
@ -360,16 +361,16 @@ bool SentAsyncWebRequest::Impl::finished() const {
|
||||||
return m_finished;
|
return m_finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SentAsyncWebRequest::Impl::error(std::string const& error) {
|
void SentAsyncWebRequest::Impl::error(std::string const& error, int code) {
|
||||||
auto lock = std::unique_lock(m_statusMutex);
|
auto lock = std::unique_lock(m_statusMutex);
|
||||||
m_statusCV.wait(lock, [this]() {
|
m_statusCV.wait(lock, [this]() {
|
||||||
return !m_paused;
|
return !m_paused;
|
||||||
});
|
});
|
||||||
Loader::get()->queueInGDThread([this, error]() {
|
Loader::get()->queueInGDThread([this, error, code]() {
|
||||||
{
|
{
|
||||||
std::lock_guard _(m_mutex);
|
std::lock_guard _(m_mutex);
|
||||||
for (auto& expect : m_expects) {
|
for (auto& expect : m_expects) {
|
||||||
expect(error);
|
expect(error, code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::lock_guard _(RUNNING_REQUESTS_MUTEX);
|
std::lock_guard _(RUNNING_REQUESTS_MUTEX);
|
||||||
|
@ -405,8 +406,8 @@ bool SentAsyncWebRequest::finished() const {
|
||||||
return m_impl->finished();
|
return m_impl->finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SentAsyncWebRequest::error(std::string const& error) {
|
void SentAsyncWebRequest::error(std::string const& error, int code) {
|
||||||
return m_impl->error(error);
|
return m_impl->error(error, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebRequest& AsyncWebRequest::join(std::string const& requestID) {
|
AsyncWebRequest& AsyncWebRequest::join(std::string const& requestID) {
|
||||||
|
@ -424,7 +425,14 @@ AsyncWebResponse AsyncWebRequest::fetch(std::string const& url) {
|
||||||
return AsyncWebResponse(*this);
|
return AsyncWebResponse(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebRequest& AsyncWebRequest::expect(std::function<void(std::string const&)> handler) {
|
AsyncWebRequest& AsyncWebRequest::expect(AsyncExpect handler) {
|
||||||
|
m_expect = [handler](std::string const& info, auto) {
|
||||||
|
return handler(info);
|
||||||
|
};
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebRequest& AsyncWebRequest::expect(AsyncExpectCode handler) {
|
||||||
m_expect = handler;
|
m_expect = handler;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue