implement the last modified since & responseHeaders in async web

This commit is contained in:
altalk23 2024-01-24 19:31:51 +03:00
parent 065d0c4926
commit df07409532
4 changed files with 111 additions and 50 deletions
loader
include/Geode/utils
src

View file

@ -60,6 +60,7 @@ namespace geode::utils::web {
using AsyncExpectCode = utils::MiniFunction<void(std::string const&, int)>;
using AsyncThen = utils::MiniFunction<void(SentAsyncWebRequest&, ByteVector const&)>;
using AsyncCancelled = utils::MiniFunction<void(SentAsyncWebRequest&)>;
using AsyncResponseHeader = utils::MiniFunction<void(std::unordered_map<std::string, std::string> const&)>;
/**
* A handle to an in-progress sent asynchronous web request. Use this to
@ -97,6 +98,11 @@ namespace geode::utils::web {
* Check if the request is finished
*/
bool finished() const;
/**
* Get the response header of the request. Only valid after the request
*/
std::string getResponseHeader(std::string_view header) const;
};
using SentAsyncWebRequestHandle = std::shared_ptr<SentAsyncWebRequest>;

View file

@ -44,24 +44,65 @@ bool s_isNewUpdateDownloaded = false;
void updater::fetchLatestGithubRelease(
const utils::MiniFunction<void(matjson::Value const&)>& then,
utils::MiniFunction<void(std::string const&)> expect
utils::MiniFunction<void(std::string const&)> expect, bool force
) {
if (s_latestGithubRelease) {
return then(s_latestGithubRelease.value());
}
std::string modifiedSince;
if (!force) {
modifiedSince = Mod::get()->getSavedValue("last-modified-github-release-check", std::string());
}
// TODO: add header to not get rate limited
web::AsyncWebRequest()
.join("loader-auto-update-check")
.header("If-Modified-Since", modifiedSince)
.userAgent("github_api/1.0")
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest")
.json()
.then([then](matjson::Value const& json) {
.then([then](web::SentAsyncWebRequest& req, matjson::Value const& json) {
Mod::get()->setSavedValue("last-modified-auto-update-check", req.getResponseHeader("Last-Modified"));
s_latestGithubRelease = json;
then(json);
})
.expect(std::move(expect));
}
void updater::downloadLatestLoaderResources() {
log::debug("Downloading latest resources", Loader::get()->getVersion().toString());
fetchLatestGithubRelease(
[](matjson::Value 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") {
updater::tryDownloadLoaderResources(
obj.needs("browser_download_url").template get<std::string>(),
false
);
return;
}
}
ResourceDownloadEvent(
UpdateFailed("Unable to find resources in latest GitHub release")
).post();
},
[](std::string const& info) {
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
).post();
},
true
);
}
void updater::tryDownloadLoaderResources(
std::string const& url,
bool tryLatestOnError
@ -118,62 +159,41 @@ void updater::updateSpecialFiles() {
void updater::downloadLoaderResources(bool useLatestRelease) {
web::AsyncWebRequest()
.join("loader-tag-exists-check")
.header("If-Modified-Since", Mod::get()->getSavedValue("last-modified-tag-exists-check", std::string()))
.userAgent("github_api/1.0")
.fetch(fmt::format(
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
Loader::get()->getVersion().toString()
))
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/tags/" + Loader::get()->getVersion().toString())
.json()
.then([](matjson::Value const& json) {
updater::tryDownloadLoaderResources(fmt::format(
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip",
Loader::get()->getVersion().toString()
), true);
})
.expect([=](std::string const& info, int code) {
if (code == 404) {
if (useLatestRelease) {
log::debug("Loader version {} does not exist on Github, downloading latest resources", Loader::get()->getVersion().toString());
fetchLatestGithubRelease(
[](matjson::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
auto root = checker.root("[]").obj();
.then([](web::SentAsyncWebRequest& req, matjson::Value const& json) {
Mod::get()->setSavedValue("last-modified-tag-exists-check", req.getResponseHeader("Last-Modified"));
auto raw = json;
JsonChecker checker(raw);
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") {
updater::tryDownloadLoaderResources(
obj.needs("browser_download_url").template get<std::string>(),
false
);
return;
}
}
ResourceDownloadEvent(
UpdateFailed("Unable to find resources in latest GitHub release")
).post();
},
[](std::string const& info) {
ResourceDownloadEvent(
UpdateFailed("Unable to download resources: " + info)
).post();
}
// find release asset
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (obj.needs("name").template get<std::string>() == "resources.zip") {
updater::tryDownloadLoaderResources(
obj.needs("browser_download_url").template get<std::string>(),
false
);
return;
}
else {
log::debug("Loader version {} does not exist on GitHub, not downloading the resources", Loader::get()->getVersion().toString());
}
ResourceDownloadEvent(
UpdateFinished()
).post();
}
ResourceDownloadEvent(
UpdateFailed("Unable to find resources in release")
).post();
})
.expect([=](std::string const& info, int code) {
if (useLatestRelease) {
log::debug("Loader version {} does not exist, trying to download latest resources");
downloadLatestLoaderResources();
}
else {
log::debug("Loader version {} does not exist on GitHub, not downloading the resources", Loader::get()->getVersion().toString());
ResourceDownloadEvent(
UpdateFailed("Unable to check if tag exists: " + info)
UpdateFinished()
).post();
}
});

View file

@ -34,10 +34,12 @@ namespace geode::updater {
void updateSpecialFiles();
void tryDownloadLoaderResources(std::string const& url, bool tryLatestOnError = true);
void downloadLoaderResources(bool useLatestRelease = false);
void downloadLatestLoaderResources();
void downloadLoaderUpdate(std::string const& url);
void fetchLatestGithubRelease(
const utils::MiniFunction<void(matjson::Value const&)>& then,
utils::MiniFunction<void(std::string const&)> expect
utils::MiniFunction<void(std::string const&)> expect,
bool force = false
);
bool verifyLoaderResources();

View file

@ -151,6 +151,7 @@ private:
std::vector<AsyncExpectCode> m_expects;
std::vector<AsyncProgress> m_progresses;
std::vector<AsyncCancelled> m_cancelleds;
std::unordered_map<std::string, std::string> m_responseHeader;
Status m_status = Status::Paused;
std::atomic<bool> m_paused = true;
std::atomic<bool> m_cancelled = false;
@ -185,6 +186,12 @@ public:
void cancel();
bool finished() const;
std::string getResponseHeader(std::string_view header) const {
auto it = m_responseHeader.find(std::string(header));
if (it == m_responseHeader.end()) return "";
return it->second;
}
friend class SentAsyncWebRequest;
};
@ -324,6 +331,28 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
std::ofstream* file;
} data{this, file.get()};
curl_easy_setopt(curl, CURLOPT_HEADERDATA, &data);
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, +[](char* buffer, size_t size, size_t nitems, void* ptr){
auto data = static_cast<ProgressData*>(ptr);
std::string header;
header.append(buffer, size * nitems);
// send the header to the response header callback
Loader::get()->queueInMainThread([self = data->self, header]() {
std::unordered_map<std::string, std::string> headers;
std::string line;
std::stringstream ss(header);
while (std::getline(ss, line)) {
auto colon = line.find(':');
if (colon == std::string::npos) continue;
auto key = line.substr(0, colon);
auto value = line.substr(colon + 1);
headers[key] = value;
}
self->m_responseHeader = std::move(headers);
});
return size * nitems;
});
curl_easy_setopt(
curl,
CURLOPT_PROGRESSFUNCTION,
@ -461,6 +490,10 @@ std::shared_ptr<SentAsyncWebRequest> SentAsyncWebRequest::create(AsyncWebRequest
ret->m_impl = std::move(std::make_shared<SentAsyncWebRequest::Impl>(ret.get(), request, id));
return ret;
}
std::string SentAsyncWebRequest::getResponseHeader(std::string_view header) const {
return m_impl->getResponseHeader(header);
}
void SentAsyncWebRequest::doCancel() {
return m_impl->doCancel();
}