mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-23 03:15:58 -04:00
rework Index + add AsyncWebRequest API + add GEODE_DEBUG macro
This commit is contained in:
parent
0646ea95f8
commit
2b06316397
16 changed files with 886 additions and 650 deletions
|
@ -19,6 +19,10 @@ set_target_properties(${PROJECT_NAME} PROPERTIES CMAKE_CONFIGURE_DEPENDS VERSION
|
|||
|
||||
target_compile_definitions(${PROJECT_NAME} INTERFACE -DPROJECT_NAME=${CMAKE_PROJECT_NAME})
|
||||
|
||||
if (GEODE_DEBUG)
|
||||
target_compile_definitions(${PROJECT_NAME} INTERFACE GEODE_DEBUG)
|
||||
endif()
|
||||
|
||||
set(GEODE_CODEGEN_PATH ${CMAKE_CURRENT_BINARY_DIR}/codegenned)
|
||||
set(GEODE_BIN_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
|
||||
set(GEODE_LOADER_PATH ${CMAKE_CURRENT_SOURCE_DIR}/loader)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.4.5
|
||||
0.4.5
|
|
@ -141,7 +141,7 @@ namespace geode {
|
|||
|
||||
template <typename ...Args>
|
||||
void debug(Args... args) {
|
||||
#ifndef NDEBUG
|
||||
#ifdef GEODE_DEBUG
|
||||
schedule(Severity::Debug, args...);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -101,6 +101,16 @@ namespace geode {
|
|||
std::function<void(FLAlertLayer*, bool)> selected,
|
||||
bool doShow = true
|
||||
);
|
||||
|
||||
GEODE_DLL FLAlertLayer* createQuickPopup(
|
||||
const char* title,
|
||||
std::string const& content,
|
||||
const char* btn1,
|
||||
const char* btn2,
|
||||
float width,
|
||||
std::function<void(FLAlertLayer*, bool)> selected,
|
||||
bool doShow = true
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -157,6 +157,11 @@ namespace geode {
|
|||
return *this;
|
||||
}
|
||||
|
||||
JsonMaybeValue<Json> array() {
|
||||
this->as<value_t::array>();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<nlohmann::detail::value_t... T>
|
||||
JsonMaybeValue<Json> asOneOf() {
|
||||
if (this->isError()) return *this;
|
||||
|
@ -273,6 +278,24 @@ namespace geode {
|
|||
}
|
||||
};
|
||||
|
||||
JsonMaybeValue<Json> at(size_t i) {
|
||||
this->as<value_t::array>();
|
||||
if (this->isError()) return *this;
|
||||
if (self().m_json.size() <= i) {
|
||||
this->setError(
|
||||
self().m_hierarchy + ": has " +
|
||||
std::to_string(self().m_json.size()) + "items "
|
||||
", expected to have at least " + std::to_string(i + 1)
|
||||
);
|
||||
return *this;
|
||||
}
|
||||
return JsonMaybeValue<Json>(
|
||||
self().m_checker, self().m_json.at(i),
|
||||
self().m_hierarchy + "." + std::to_string(i),
|
||||
self().m_hasValue
|
||||
);
|
||||
}
|
||||
|
||||
Iterator<JsonMaybeValue<Json>> iterate() {
|
||||
this->as<value_t::array>();
|
||||
Iterator<JsonMaybeValue<Json>> iter;
|
||||
|
|
|
@ -4,10 +4,18 @@
|
|||
#include <fs/filesystem.hpp>
|
||||
#include "Result.hpp"
|
||||
#include "json.hpp"
|
||||
#include <mutex>
|
||||
|
||||
namespace geode::utils::web {
|
||||
using FileProgressCallback = std::function<bool(double, double)>;
|
||||
|
||||
/**
|
||||
* Synchronously fetch data from the internet
|
||||
* @param url URL to fetch
|
||||
* @returns Returned data as bytes, or error on error
|
||||
*/
|
||||
GEODE_DLL Result<byte_array> fetchBytes(std::string const& url);
|
||||
|
||||
/**
|
||||
* Synchronously fetch data from the internet
|
||||
* @param url URL to fetch
|
||||
|
@ -46,5 +54,154 @@ namespace geode::utils::web {
|
|||
return Err(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
class SentAsyncWebRequest;
|
||||
template<class T>
|
||||
class AsyncWebResult;
|
||||
class AsyncWebResponse;
|
||||
class AsyncWebRequest;
|
||||
|
||||
using AsyncProgress = std::function<void(SentAsyncWebRequest&, double, double)>;
|
||||
using AsyncExpect = std::function<void(std::string const&)>;
|
||||
using AsyncThen = std::function<void(SentAsyncWebRequest&, byte_array const&)>;
|
||||
using AsyncCancelled = std::function<void(SentAsyncWebRequest&)>;
|
||||
|
||||
class SentAsyncWebRequest {
|
||||
private:
|
||||
std::string m_id;
|
||||
std::string m_url;
|
||||
std::vector<AsyncThen> m_thens;
|
||||
std::vector<AsyncExpect> m_expects;
|
||||
std::vector<AsyncProgress> m_progresses;
|
||||
std::vector<AsyncCancelled> m_cancelleds;
|
||||
std::atomic<bool> m_paused = true;
|
||||
std::atomic<bool> m_cancelled = false;
|
||||
std::atomic<bool> m_finished = false;
|
||||
std::atomic<bool> m_cleanedUp = false;
|
||||
mutable std::mutex m_mutex;
|
||||
std::variant<
|
||||
std::monostate,
|
||||
std::ostream*,
|
||||
ghc::filesystem::path
|
||||
> m_target = std::monostate();
|
||||
|
||||
template<class T>
|
||||
friend class AsyncWebResult;
|
||||
friend class AsyncWebRequest;
|
||||
|
||||
void pause();
|
||||
void resume();
|
||||
void error(std::string const& error);
|
||||
void doCancel();
|
||||
|
||||
public:
|
||||
SentAsyncWebRequest(AsyncWebRequest const&, std::string const& id);
|
||||
|
||||
void cancel();
|
||||
bool finished() const;
|
||||
};
|
||||
|
||||
using SentAsyncWebRequestHandle = std::shared_ptr<SentAsyncWebRequest>;
|
||||
|
||||
template<class T>
|
||||
using DataConverter = Result<T>(*)(byte_array const&);
|
||||
|
||||
template<class T>
|
||||
class AsyncWebResult {
|
||||
private:
|
||||
AsyncWebRequest& m_request;
|
||||
DataConverter<T> m_converter;
|
||||
|
||||
AsyncWebResult(AsyncWebRequest& request, DataConverter<T> converter)
|
||||
: m_request(request), m_converter(converter) {}
|
||||
|
||||
friend class AsyncWebResponse;
|
||||
|
||||
public:
|
||||
AsyncWebRequest& then(std::function<void(T)> handle) {
|
||||
m_request.m_then = [
|
||||
converter = m_converter,
|
||||
handle
|
||||
](SentAsyncWebRequest& req, byte_array const& arr) {
|
||||
auto conv = converter(arr);
|
||||
if (conv) {
|
||||
handle(conv.value());
|
||||
} else {
|
||||
req.error("Unable to convert value: " + conv.error());
|
||||
}
|
||||
};
|
||||
return m_request;
|
||||
}
|
||||
|
||||
AsyncWebRequest& then(std::function<void(SentAsyncWebRequest&, T)> handle) {
|
||||
m_request.m_then = [
|
||||
converter = m_converter,
|
||||
handle
|
||||
](SentAsyncWebRequest& req, byte_array const& arr) {
|
||||
auto conv = converter(arr);
|
||||
if (conv) {
|
||||
handle(req, conv.value());
|
||||
} else {
|
||||
req.error("Unable to convert value: " + conv.error());
|
||||
}
|
||||
};
|
||||
return m_request;
|
||||
}
|
||||
};
|
||||
|
||||
class GEODE_DLL AsyncWebResponse {
|
||||
private:
|
||||
AsyncWebRequest& m_request;
|
||||
|
||||
inline AsyncWebResponse(AsyncWebRequest& request) : m_request(request) {}
|
||||
|
||||
friend class AsyncWebRequest;
|
||||
|
||||
public:
|
||||
// Make sure the stream lives for the entire duration of the request.
|
||||
AsyncWebResult<std::monostate> into(std::ostream& stream);
|
||||
AsyncWebResult<std::monostate> into(ghc::filesystem::path const& path);
|
||||
AsyncWebResult<std::string> text();
|
||||
AsyncWebResult<byte_array> bytes();
|
||||
AsyncWebResult<nlohmann::json> json();
|
||||
|
||||
template<class T>
|
||||
AsyncWebResult<T> as(DataConverter<T> converter) {
|
||||
return AsyncWebResult(m_request, converter);
|
||||
}
|
||||
};
|
||||
|
||||
class GEODE_DLL AsyncWebRequest {
|
||||
private:
|
||||
std::optional<std::string> m_joinID;
|
||||
std::string m_url;
|
||||
AsyncThen m_then = nullptr;
|
||||
AsyncExpect m_expect = nullptr;
|
||||
AsyncProgress m_progress = nullptr;
|
||||
AsyncCancelled m_cancelled = nullptr;
|
||||
bool m_sent = false;
|
||||
std::variant<
|
||||
std::monostate,
|
||||
std::ostream*,
|
||||
ghc::filesystem::path
|
||||
> m_target;
|
||||
|
||||
template<class T>
|
||||
friend class AsyncWebResult;
|
||||
friend class SentAsyncWebRequest;
|
||||
friend class AsyncWebResponse;
|
||||
|
||||
public:
|
||||
AsyncWebRequest& join(std::string const& requestID);
|
||||
AsyncWebResponse fetch(std::string const& url);
|
||||
AsyncWebRequest& expect(AsyncExpect handler);
|
||||
AsyncWebRequest& progress(AsyncProgress progressFunc);
|
||||
// Web requests may be cancelled after they are finished (for example,
|
||||
// if downloading files in bulk and one fails). In that case, handle
|
||||
// freeing up the results of `then` here
|
||||
AsyncWebRequest& cancelled(AsyncCancelled cancelledFunc);
|
||||
SentAsyncWebRequestHandle send();
|
||||
~AsyncWebRequest();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -176,7 +176,7 @@ namespace geode::utils::vector {
|
|||
* @returns Reference to vector.
|
||||
*/
|
||||
template<class T>
|
||||
std::vector<T>& erase(std::vector<T>& vec, T& element) {
|
||||
std::vector<T>& erase(std::vector<T>& vec, T const& element) {
|
||||
vec.erase(std::remove(vec.begin(), vec.end(), element), vec.end());
|
||||
return vec;
|
||||
}
|
||||
|
|
|
@ -31,42 +31,6 @@ static void addUpdateIcon(const char* icon = "updates-available.png"_spr) {
|
|||
}
|
||||
}
|
||||
|
||||
static void updateModsProgress(
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t progress
|
||||
) {
|
||||
if (status == UpdateStatus::Failed) {
|
||||
g_indexUpdateNotif->hide();
|
||||
g_indexUpdateNotif = nullptr;
|
||||
NotificationBuilder()
|
||||
.title("Some Updates Failed")
|
||||
.text("Some mods failed to update, click for details")
|
||||
.icon("info-alert.png"_spr)
|
||||
.clicked([info](auto) -> void {
|
||||
FLAlertLayer::create("Info", info, "OK")->show();
|
||||
})
|
||||
.show();
|
||||
addUpdateIcon("updates-failed.png"_spr);
|
||||
}
|
||||
|
||||
if (status == UpdateStatus::Finished) {
|
||||
g_indexUpdateNotif->hide();
|
||||
g_indexUpdateNotif = nullptr;
|
||||
NotificationBuilder()
|
||||
.title("Updates Installed")
|
||||
.text(
|
||||
"Mods have been updated, please "
|
||||
"restart to apply changes"
|
||||
)
|
||||
.icon("updates-available.png"_spr)
|
||||
.clicked([info](auto) -> void {
|
||||
FLAlertLayer::create("Info", info, "OK")->show();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
static void updateIndexProgress(
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
|
@ -87,44 +51,14 @@ static void updateIndexProgress(
|
|||
g_indexUpdateNotif->hide();
|
||||
g_indexUpdateNotif = nullptr;
|
||||
if (Index::get()->areUpdatesAvailable()) {
|
||||
if (Mod::get()->getSettingValue<bool>("auto-update-mods")) {
|
||||
auto ticket = Index::get()->installUpdates(updateModsProgress);
|
||||
if (!ticket) {
|
||||
NotificationBuilder()
|
||||
.title("Unable to auto-update")
|
||||
.text("Unable to update mods :(")
|
||||
.icon("updates-failed.png"_spr)
|
||||
.show();
|
||||
} else {
|
||||
g_indexUpdateNotif = NotificationBuilder()
|
||||
.title("Installing updates")
|
||||
.text("Installing updates...")
|
||||
.clicked([ticket](auto) -> void {
|
||||
createQuickPopup(
|
||||
"Cancel Updates",
|
||||
"Do you want to <cr>cancel</c> updates?",
|
||||
"Don't Cancel", "Cancel Updates",
|
||||
[ticket](auto, bool btn2) -> void {
|
||||
if (g_indexUpdateNotif && btn2) {
|
||||
ticket.value()->cancel();
|
||||
}
|
||||
}
|
||||
);
|
||||
}, false)
|
||||
.loading()
|
||||
.stay()
|
||||
.show();
|
||||
}
|
||||
} else {
|
||||
NotificationBuilder()
|
||||
.title("Updates available")
|
||||
.text("Some mods have updates available!")
|
||||
.icon("updates-available.png"_spr)
|
||||
.clicked([](auto) -> void {
|
||||
ModListLayer::scene();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
NotificationBuilder()
|
||||
.title("Updates available")
|
||||
.text("Some mods have updates available!")
|
||||
.icon("updates-available.png"_spr)
|
||||
.clicked([](auto) -> void {
|
||||
ModListLayer::scene();
|
||||
})
|
||||
.show();
|
||||
addUpdateIcon();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <Geode/utils/json.hpp>
|
||||
#include <Geode/utils/JsonValidation.hpp>
|
||||
#include <Geode/utils/fetch.hpp>
|
||||
#include <hash.hpp>
|
||||
|
||||
#define GITHUB_DONT_RATE_LIMIT_ME_PLS 0
|
||||
|
||||
|
@ -59,159 +60,17 @@ bool Index::isFeaturedItem(std::string const& item) const {
|
|||
return m_featured.count(item);
|
||||
}
|
||||
|
||||
void Index::updateIndexThread(bool force) {
|
||||
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
|
||||
|
||||
// download index
|
||||
|
||||
#if GITHUB_DONT_RATE_LIMIT_ME_PLS == 0
|
||||
|
||||
indexUpdateProgress(
|
||||
UpdateStatus::Progress, "Fetching index metadata", 0
|
||||
);
|
||||
|
||||
// get all commits in index repo
|
||||
auto commit = web::fetchJSON(
|
||||
"https://api.github.com/repos/geode-sdk/mods/commits"
|
||||
);
|
||||
if (!commit) {
|
||||
return indexUpdateProgress(UpdateStatus::Failed, commit.error());
|
||||
}
|
||||
auto json = commit.value();
|
||||
if (
|
||||
json.is_object() &&
|
||||
json.contains("documentation_url") &&
|
||||
json.contains("message")
|
||||
) {
|
||||
// whoops! got rate limited
|
||||
return indexUpdateProgress(
|
||||
UpdateStatus::Failed,
|
||||
json["message"].get<std::string>()
|
||||
);
|
||||
}
|
||||
|
||||
indexUpdateProgress(
|
||||
UpdateStatus::Progress, "Checking index status", 25
|
||||
);
|
||||
|
||||
// read sha of latest commit
|
||||
|
||||
if (!json.is_array()) {
|
||||
return indexUpdateProgress(
|
||||
UpdateStatus::Failed,
|
||||
"Fetched commits, expected 'array', got '" +
|
||||
std::string(json.type_name()) + "'. "
|
||||
"Report this bug to the Geode developers!"
|
||||
);
|
||||
}
|
||||
|
||||
if (!json.at(0).is_object()) {
|
||||
return indexUpdateProgress(
|
||||
UpdateStatus::Failed,
|
||||
"Fetched commits, expected 'array.object', got 'array." +
|
||||
std::string(json.type_name()) + "'. "
|
||||
"Report this bug to the Geode developers!"
|
||||
);
|
||||
}
|
||||
|
||||
if (!json.at(0).contains("sha")) {
|
||||
return indexUpdateProgress(
|
||||
UpdateStatus::Failed,
|
||||
"Fetched commits, missing '0.sha'. "
|
||||
"Report this bug to the Geode developers!"
|
||||
);
|
||||
}
|
||||
|
||||
auto upcomingCommitSHA = json.at(0)["sha"];
|
||||
|
||||
// read sha of currently installed commit
|
||||
std::string currentCommitSHA = "";
|
||||
if (ghc::filesystem::exists(indexDir / "current")) {
|
||||
auto data = utils::file::readString(indexDir / "current");
|
||||
if (data) {
|
||||
currentCommitSHA = data.value();
|
||||
}
|
||||
}
|
||||
|
||||
// update if forced or latest commit has
|
||||
// different sha
|
||||
if (force || currentCommitSHA != upcomingCommitSHA) {
|
||||
// save new sha in file
|
||||
utils::file::writeString(indexDir / "current", upcomingCommitSHA);
|
||||
|
||||
// download latest commit (by downloading
|
||||
// the repo as a zip)
|
||||
|
||||
indexUpdateProgress(
|
||||
UpdateStatus::Progress,
|
||||
"Downloading index",
|
||||
50
|
||||
);
|
||||
auto gotZip = web::fetchFile(
|
||||
"https://github.com/geode-sdk/mods/zipball/main",
|
||||
indexDir / "index.zip"
|
||||
);
|
||||
if (!gotZip) {
|
||||
return indexUpdateProgress(
|
||||
UpdateStatus::Failed,
|
||||
gotZip.error()
|
||||
);
|
||||
}
|
||||
|
||||
// delete old index
|
||||
if (ghc::filesystem::exists(indexDir / "index")) {
|
||||
ghc::filesystem::remove_all(indexDir / "index");
|
||||
}
|
||||
|
||||
auto unzip = file::unzipTo(indexDir / "index.zip", indexDir);
|
||||
if (!unzip) {
|
||||
return indexUpdateProgress(
|
||||
UpdateStatus::Failed, unzip.error()
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// update index
|
||||
|
||||
indexUpdateProgress(
|
||||
UpdateStatus::Progress,
|
||||
"Updating index",
|
||||
75
|
||||
);
|
||||
this->updateIndexFromLocalCache();
|
||||
|
||||
m_upToDate = true;
|
||||
m_updating = false;
|
||||
|
||||
indexUpdateProgress(
|
||||
UpdateStatus::Finished,
|
||||
"",
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
void Index::indexUpdateProgress(
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t percentage
|
||||
) {
|
||||
Loader::get()->queueInGDThread([this, status, info, percentage]() -> void {
|
||||
m_callbacksMutex.lock();
|
||||
for (auto& d : m_callbacks) {
|
||||
d(status, info, percentage);
|
||||
}
|
||||
if (
|
||||
status == UpdateStatus::Finished ||
|
||||
status == UpdateStatus::Failed
|
||||
) {
|
||||
m_callbacks.clear();
|
||||
}
|
||||
m_callbacksMutex.unlock();
|
||||
});
|
||||
}
|
||||
|
||||
void Index::updateIndex(IndexUpdateCallback callback, bool force) {
|
||||
#define RETURN_ERROR(str) \
|
||||
std::string err__ = (str); \
|
||||
if (callback) callback( \
|
||||
UpdateStatus::Failed, \
|
||||
err__, \
|
||||
0 \
|
||||
); \
|
||||
log::info("Index update failed: {}", err__);\
|
||||
return;
|
||||
|
||||
// if already updated and no force, let
|
||||
// delegate know
|
||||
if (!force && m_upToDate) {
|
||||
|
@ -225,36 +84,134 @@ void Index::updateIndex(IndexUpdateCallback callback, bool force) {
|
|||
return;
|
||||
}
|
||||
|
||||
// add delegate thread-safely if it's not
|
||||
// added already
|
||||
if (callback) {
|
||||
m_callbacksMutex.lock();
|
||||
m_callbacks.push_back(callback);
|
||||
m_callbacksMutex.unlock();
|
||||
}
|
||||
|
||||
// if already updating, let delegate know
|
||||
// and return
|
||||
if (m_updating) {
|
||||
if (callback) {
|
||||
callback(
|
||||
UpdateStatus::Progress,
|
||||
"Waiting for update info",
|
||||
0
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_updating = true;
|
||||
|
||||
// create directory for the local clone of
|
||||
// the index
|
||||
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
|
||||
ghc::filesystem::create_directories(indexDir);
|
||||
|
||||
// update index in another thread to avoid
|
||||
// pausing UI
|
||||
std::thread(&Index::updateIndexThread, this, force).detach();
|
||||
#if GITHUB_DONT_RATE_LIMIT_ME_PLS == 1
|
||||
|
||||
auto err = this->updateIndexFromLocalCache();
|
||||
if (!err) {
|
||||
RETURN_ERROR(err);
|
||||
}
|
||||
|
||||
m_upToDate = true;
|
||||
m_updating = false;
|
||||
|
||||
if (callback) callback(UpdateStatus::Finished, "", 100);
|
||||
return;
|
||||
|
||||
#endif
|
||||
|
||||
web::AsyncWebRequest()
|
||||
.join("index-update")
|
||||
.fetch("https://api.github.com/repos/geode-sdk/mods/commits")
|
||||
.json()
|
||||
.then([this, force, callback](nlohmann::json const& json) {
|
||||
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
|
||||
|
||||
// check if rate-limited (returns object)
|
||||
JsonChecker checkerObj(json);
|
||||
auto obj = checkerObj.root("[geode-sdk/mods/commits]").obj();
|
||||
if (obj.has("documentation_url") && obj.has("message")) {
|
||||
RETURN_ERROR(obj.has("message").get<std::string>());
|
||||
}
|
||||
|
||||
// get sha of latest commit
|
||||
JsonChecker checker(json);
|
||||
auto root = checker.root("[geode-sdk/mods/commits]").array();
|
||||
|
||||
std::string upcomingCommitSHA;
|
||||
if (auto first = root.at(0).obj().needs("sha")) {
|
||||
upcomingCommitSHA = first.get<std::string>();
|
||||
} else {
|
||||
RETURN_ERROR("Unable to get hash from latest commit: " + checker.getError());
|
||||
}
|
||||
|
||||
// read sha of currently installed commit
|
||||
std::string currentCommitSHA = "";
|
||||
if (ghc::filesystem::exists(indexDir / "current")) {
|
||||
auto data = utils::file::readString(indexDir / "current");
|
||||
if (data) {
|
||||
currentCommitSHA = data.value();
|
||||
}
|
||||
}
|
||||
|
||||
// update if forced or latest commit has
|
||||
// different sha
|
||||
if (force || currentCommitSHA != upcomingCommitSHA) {
|
||||
// save new sha in file
|
||||
utils::file::writeString(indexDir / "current", upcomingCommitSHA);
|
||||
|
||||
web::AsyncWebRequest()
|
||||
.join("index-download")
|
||||
.fetch("https://github.com/geode-sdk/mods/zipball/main")
|
||||
.into(indexDir / "index.zip")
|
||||
.then([this, indexDir, callback](auto) {
|
||||
// delete old index
|
||||
try {
|
||||
if (ghc::filesystem::exists(indexDir / "index")) {
|
||||
ghc::filesystem::remove_all(indexDir / "index");
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
RETURN_ERROR("Unable to delete old index " + std::string(e.what()));
|
||||
}
|
||||
|
||||
// unzip new index
|
||||
auto unzip = file::unzipTo(indexDir / "index.zip", indexDir);
|
||||
if (!unzip) {
|
||||
RETURN_ERROR(unzip.error());
|
||||
}
|
||||
|
||||
// update index
|
||||
auto err = this->updateIndexFromLocalCache();
|
||||
if (!err) {
|
||||
RETURN_ERROR(err.error());
|
||||
}
|
||||
|
||||
m_upToDate = true;
|
||||
m_updating = false;
|
||||
|
||||
if (callback) callback(
|
||||
UpdateStatus::Finished, "", 100
|
||||
);
|
||||
})
|
||||
.expect([callback](std::string const& err) {
|
||||
RETURN_ERROR(err);
|
||||
})
|
||||
.progress([callback](web::SentAsyncWebRequest& req, double now, double total) {
|
||||
if (callback) callback(
|
||||
UpdateStatus::Progress,
|
||||
"Downloading",
|
||||
static_cast<int>(now / total * 100.0)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
auto err = this->updateIndexFromLocalCache();
|
||||
if (!err) {
|
||||
RETURN_ERROR(err.error());
|
||||
}
|
||||
|
||||
m_upToDate = true;
|
||||
m_updating = false;
|
||||
|
||||
if (callback) callback(
|
||||
UpdateStatus::Finished,
|
||||
"", 100
|
||||
);
|
||||
}
|
||||
})
|
||||
.expect([callback](std::string const& err) {
|
||||
RETURN_ERROR(err);
|
||||
})
|
||||
.progress([callback](web::SentAsyncWebRequest& req, double now, double total) {
|
||||
if (callback) callback(
|
||||
UpdateStatus::Progress,
|
||||
"Downloading",
|
||||
static_cast<int>(now / total * 100.0)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
|
||||
|
@ -338,7 +295,7 @@ void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
|
|||
}
|
||||
}
|
||||
|
||||
void Index::updateIndexFromLocalCache() {
|
||||
Result<> Index::updateIndexFromLocalCache() {
|
||||
m_items.clear();
|
||||
auto baseIndexDir = Loader::get()->getGeodeDirectory() / "index";
|
||||
|
||||
|
@ -352,10 +309,19 @@ void Index::updateIndexFromLocalCache() {
|
|||
|
||||
// load index mods
|
||||
auto modsDir = baseIndexDir / "index";
|
||||
for (auto const& dir : ghc::filesystem::directory_iterator(modsDir)) {
|
||||
if (ghc::filesystem::is_directory(dir)) {
|
||||
this->addIndexItemFromFolder(dir);
|
||||
if (ghc::filesystem::exists(modsDir)) {
|
||||
for (auto const& dir : ghc::filesystem::directory_iterator(modsDir)) {
|
||||
if (ghc::filesystem::is_directory(dir)) {
|
||||
this->addIndexItemFromFolder(dir);
|
||||
}
|
||||
}
|
||||
log::info("Index updated");
|
||||
return Ok();
|
||||
} else {
|
||||
return Err(
|
||||
"Index appears not to have been "
|
||||
"downloaded, or is fully empty"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -446,9 +412,8 @@ Result<std::vector<std::string>> Index::checkDependenciesForItem(
|
|||
}
|
||||
}
|
||||
|
||||
Result<InstallTicket*> Index::installItems(
|
||||
std::vector<IndexItem> const& items,
|
||||
ItemInstallCallback progress
|
||||
Result<InstallItems> Index::installItems(
|
||||
std::vector<IndexItem> const& items
|
||||
) {
|
||||
std::vector<std::string> ids {};
|
||||
for (auto& item : items) {
|
||||
|
@ -482,14 +447,13 @@ Result<InstallTicket*> Index::installItems(
|
|||
}
|
||||
utils::vector::push(ids, list.value());
|
||||
}
|
||||
return Ok(new InstallTicket(this, ids, progress));
|
||||
return Ok(InstallItems(ids));
|
||||
}
|
||||
|
||||
Result<InstallTicket*> Index::installItem(
|
||||
IndexItem const& item,
|
||||
ItemInstallCallback progress
|
||||
Result<InstallItems> Index::installItem(
|
||||
IndexItem const& item
|
||||
) {
|
||||
return this->installItems({ item }, progress);
|
||||
return this->installItems({ item });
|
||||
}
|
||||
|
||||
bool Index::isUpdateAvailableForItem(std::string const& id) const {
|
||||
|
@ -522,9 +486,7 @@ bool Index::areUpdatesAvailable() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
Result<InstallTicket*> Index::installUpdates(
|
||||
IndexUpdateCallback callback, bool force
|
||||
) {
|
||||
Result<InstallItems> Index::installAllUpdates() {
|
||||
// find items that need updating
|
||||
std::vector<IndexItem> itemsToUpdate {};
|
||||
for (auto& item : m_items) {
|
||||
|
@ -532,52 +494,118 @@ Result<InstallTicket*> Index::installUpdates(
|
|||
itemsToUpdate.push_back(item);
|
||||
}
|
||||
}
|
||||
return this->installItems(itemsToUpdate);
|
||||
}
|
||||
|
||||
// generate ticket
|
||||
auto ticket = this->installItems(
|
||||
itemsToUpdate,
|
||||
[itemsToUpdate, callback](
|
||||
InstallTicket*,
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t progress
|
||||
) -> void {
|
||||
switch (status) {
|
||||
case UpdateStatus::Failed: {
|
||||
callback(
|
||||
UpdateStatus::Failed,
|
||||
"Updating failed: " + info,
|
||||
InstallHandles Index::getRunningInstallations() const {
|
||||
return map::getValues(m_installations);
|
||||
}
|
||||
|
||||
InstallHandle Index::isInstallingItem(std::string const& id) {
|
||||
if (m_installations.count(id)) {
|
||||
return m_installations.at(id);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::string> InstallItems::toInstall() const {
|
||||
return m_toInstall;
|
||||
}
|
||||
|
||||
InstallHandles InstallItems::begin(ItemInstallCallback callback) const {
|
||||
InstallHandles res {};
|
||||
|
||||
for (auto& inst : m_toInstall) {
|
||||
// by virtue of running this function we know item must be valid
|
||||
auto item = Index::get()->getKnownItem(inst);
|
||||
|
||||
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
|
||||
auto tempFile = indexDir / item.m_download.m_filename;
|
||||
|
||||
auto handle = web::AsyncWebRequest()
|
||||
.join("install_mod_" + inst)
|
||||
.fetch(item.m_download.m_url)
|
||||
.into(tempFile)
|
||||
.then([callback, item, inst, indexDir, tempFile](auto) {
|
||||
// check for 404
|
||||
auto notFound = utils::file::readString(tempFile);
|
||||
if (notFound && notFound.value() == "Not Found") {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
if (callback) callback(
|
||||
inst, UpdateStatus::Failed,
|
||||
"Binary file download returned \"Not found\". Report "
|
||||
"this to the Geode development team.",
|
||||
0
|
||||
);
|
||||
} break;
|
||||
}
|
||||
|
||||
case UpdateStatus::Finished: {
|
||||
std::string updatedStr = "";
|
||||
for (auto& item : itemsToUpdate) {
|
||||
updatedStr += item.m_info.m_name + " (" +
|
||||
item.m_info.m_id + ")\n";
|
||||
}
|
||||
callback(
|
||||
UpdateStatus::Finished,
|
||||
"Updated the following mods: " +
|
||||
updatedStr +
|
||||
"Please restart to apply changes.",
|
||||
100
|
||||
// verify checksum
|
||||
if (callback) callback(inst, UpdateStatus::Progress, "Verifying", 100);
|
||||
if (::calculateHash(tempFile.string()) != item.m_download.m_hash) {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
if (callback) callback(
|
||||
inst, UpdateStatus::Failed,
|
||||
"Checksum mismatch! (Downloaded file did not match what "
|
||||
"was expected. Try again, and if the download fails another time, "
|
||||
"report this to the Geode development team.",
|
||||
0
|
||||
);
|
||||
} break;
|
||||
}
|
||||
|
||||
case UpdateStatus::Progress: {
|
||||
callback(UpdateStatus::Progress, info, progress);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!ticket) {
|
||||
return Err(ticket.error());
|
||||
// move temp file to geode directory
|
||||
try {
|
||||
auto modDir = Loader::get()->getGeodeDirectory() / "mods";
|
||||
auto targetFile = modDir / item.m_download.m_filename;
|
||||
|
||||
// find valid filename that doesn't exist yet
|
||||
auto filename = ghc::filesystem::path(
|
||||
item.m_download.m_filename
|
||||
).replace_extension("").string();
|
||||
|
||||
size_t number = 0;
|
||||
while (ghc::filesystem::exists(targetFile)) {
|
||||
targetFile = modDir /
|
||||
(filename + std::to_string(number) + ".geode");
|
||||
number++;
|
||||
}
|
||||
|
||||
// move file
|
||||
ghc::filesystem::rename(tempFile, targetFile);
|
||||
} catch(std::exception& e) {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
if (callback) callback(
|
||||
inst, UpdateStatus::Failed,
|
||||
"Unable to move downloaded file to mods directory: \"" +
|
||||
std::string(e.what()) + " \" "
|
||||
"(This might be due to insufficient permissions to "
|
||||
"write files under SteamLibrary, try running GD as "
|
||||
"administrator)",
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
// finished
|
||||
if (callback) callback(inst, UpdateStatus::Finished, "", 100);
|
||||
})
|
||||
.expect([inst, callback](std::string const& error) {
|
||||
if (callback) callback(inst, UpdateStatus::Failed, error, 0);
|
||||
Index::get()->m_installations.erase(inst);
|
||||
})
|
||||
.cancelled([inst](auto&) {
|
||||
Index::get()->m_installations.erase(inst);
|
||||
})
|
||||
.progress([inst, callback](web::SentAsyncWebRequest&, double now, double total) {
|
||||
if (callback) callback(
|
||||
inst, UpdateStatus::Progress,
|
||||
"Downloading binary",
|
||||
static_cast<uint8_t>(now / total * 100.0)
|
||||
);
|
||||
})
|
||||
.send();
|
||||
|
||||
res.push_back(handle);
|
||||
Index::get()->m_installations.insert({ inst, handle });
|
||||
}
|
||||
|
||||
// install updates concurrently
|
||||
ticket.value()->start(InstallMode::Concurrent);
|
||||
|
||||
return ticket;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
#include <Geode/Geode.hpp>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <Geode/utils/fetch.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
class Index;
|
||||
struct ModInstallUpdate;
|
||||
class InstallTicket;
|
||||
|
||||
// todo: make index use events
|
||||
|
||||
|
@ -18,8 +18,11 @@ enum class UpdateStatus {
|
|||
Finished,
|
||||
};
|
||||
|
||||
using InstallHandle = web::SentAsyncWebRequestHandle;
|
||||
using InstallHandles = std::vector<InstallHandle>;
|
||||
|
||||
using ItemInstallCallback = std::function<void(
|
||||
InstallTicket*, UpdateStatus, std::string const&, uint8_t
|
||||
std::string const&, UpdateStatus, std::string const&, uint8_t
|
||||
)>;
|
||||
using IndexUpdateCallback = std::function<void(
|
||||
UpdateStatus, std::string const&, uint8_t
|
||||
|
@ -39,73 +42,20 @@ struct IndexItem {
|
|||
std::unordered_set<std::string> m_categories;
|
||||
};
|
||||
|
||||
enum class InstallMode {
|
||||
Order, // download & install one-by-one
|
||||
Concurrent, // download & install all simultaneously
|
||||
};
|
||||
struct InstallItems final {
|
||||
private:
|
||||
std::vector<std::string> m_toInstall;
|
||||
|
||||
/**
|
||||
* Used for working with a currently
|
||||
* happening mod installation from
|
||||
* the index. Note that once the
|
||||
* installation is finished / failed,
|
||||
* the ticket will free its own memory,
|
||||
* so make sure to let go of any
|
||||
* pointers you may have to it.
|
||||
*/
|
||||
class InstallTicket {
|
||||
protected:
|
||||
ItemInstallCallback m_progress;
|
||||
const std::vector<std::string> m_installList;
|
||||
mutable std::mutex m_cancelMutex;
|
||||
bool m_cancelling = false;
|
||||
bool m_installing = false;
|
||||
bool m_replaceFiles = true;
|
||||
Index* m_index;
|
||||
|
||||
void postProgress(
|
||||
UpdateStatus status,
|
||||
std::string const& info = "",
|
||||
uint8_t progress = 0
|
||||
);
|
||||
void install(std::string const& id);
|
||||
inline InstallItems(
|
||||
std::vector<std::string> const& toInstall
|
||||
) : m_toInstall(toInstall) {}
|
||||
|
||||
friend class Index;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create a new ticket for installing a list of mods. This method
|
||||
* should not be called manually; instead, you should always use
|
||||
* `Index::installItem`. Note that once the installation is
|
||||
* finished / failed, the ticket will free its own memory, so make
|
||||
* sure to let go of any pointers you may have to it.
|
||||
*/
|
||||
InstallTicket(
|
||||
Index* index,
|
||||
std::vector<std::string> const& list,
|
||||
ItemInstallCallback progress
|
||||
);
|
||||
std::vector<std::string> toInstall() const;
|
||||
|
||||
/**
|
||||
* Get list of mods to install
|
||||
*/
|
||||
std::vector<std::string> getInstallList() const;
|
||||
|
||||
/**
|
||||
* Cancel all pending installations and revert finished ones. This
|
||||
* function is thread-safe
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
/**
|
||||
* Begin installation. Note that this function is *not*
|
||||
* thread-safe
|
||||
* @param mode Whether to install the list of mods
|
||||
* provided concurrently or in order
|
||||
* @note Use InstallTicket::cancel to cancel the
|
||||
* installation
|
||||
*/
|
||||
void start(InstallMode mode = InstallMode::Concurrent);
|
||||
InstallHandles begin(ItemInstallCallback callback) const;
|
||||
};
|
||||
|
||||
class Index {
|
||||
|
@ -113,50 +63,42 @@ protected:
|
|||
bool m_upToDate = false;
|
||||
bool m_updating = false;
|
||||
mutable std::mutex m_callbacksMutex;
|
||||
std::vector<IndexUpdateCallback> m_callbacks;
|
||||
std::vector<IndexItem> m_items;
|
||||
std::unordered_map<std::string, InstallHandle> m_installations;
|
||||
mutable std::mutex m_ticketsMutex;
|
||||
std::unordered_set<std::string> m_featured;
|
||||
std::unordered_set<std::string> m_categories;
|
||||
|
||||
void indexUpdateProgress(
|
||||
UpdateStatus status,
|
||||
std::string const& info = "",
|
||||
uint8_t percentage = 0
|
||||
);
|
||||
|
||||
void updateIndexThread(bool force);
|
||||
void addIndexItemFromFolder(ghc::filesystem::path const& dir);
|
||||
void updateIndexFromLocalCache();
|
||||
Result<> updateIndexFromLocalCache();
|
||||
|
||||
Result<std::vector<std::string>> checkDependenciesForItem(
|
||||
IndexItem const& item
|
||||
);
|
||||
|
||||
friend struct InstallItems;
|
||||
|
||||
public:
|
||||
static Index* get();
|
||||
|
||||
std::vector<IndexItem> getItems() const;
|
||||
bool isKnownItem(std::string const& id) const;
|
||||
IndexItem getKnownItem(std::string const& id) const;
|
||||
Result<InstallTicket*> installItems(
|
||||
std::vector<IndexItem> const& item,
|
||||
ItemInstallCallback progress = nullptr
|
||||
);
|
||||
Result<InstallTicket*> installItem(
|
||||
IndexItem const& item,
|
||||
ItemInstallCallback progress = nullptr
|
||||
);
|
||||
bool isUpdateAvailableForItem(std::string const& id) const;
|
||||
bool isUpdateAvailableForItem(IndexItem const& item) const;
|
||||
bool areUpdatesAvailable() const;
|
||||
Result<InstallTicket*> installUpdates(
|
||||
IndexUpdateCallback callback = nullptr,
|
||||
bool force = false
|
||||
);
|
||||
|
||||
std::unordered_set<std::string> getCategories() const;
|
||||
std::vector<IndexItem> getFeaturedItems() const;
|
||||
bool isFeaturedItem(std::string const& item) const;
|
||||
|
||||
Result<InstallItems> installItems(std::vector<IndexItem> const& item);
|
||||
Result<InstallItems> installItem(IndexItem const& item);
|
||||
InstallHandles getRunningInstallations() const;
|
||||
InstallHandle isInstallingItem(std::string const& id);
|
||||
|
||||
bool isUpdateAvailableForItem(std::string const& id) const;
|
||||
bool isUpdateAvailableForItem(IndexItem const& item) const;
|
||||
bool areUpdatesAvailable() const;
|
||||
Result<InstallItems> installAllUpdates();
|
||||
|
||||
bool isIndexUpdated() const;
|
||||
void updateIndex(IndexUpdateCallback callback, bool force = false);
|
||||
};
|
||||
|
|
|
@ -1,201 +0,0 @@
|
|||
#include "Index.hpp"
|
||||
#include <thread>
|
||||
#include <Geode/utils/json.hpp>
|
||||
#include <hash.hpp>
|
||||
#include <Geode/utils/fetch.hpp>
|
||||
|
||||
void InstallTicket::postProgress(
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t percentage
|
||||
) {
|
||||
if (m_progress) {
|
||||
Loader::get()->queueInGDThread([this, status, info, percentage]() -> void {
|
||||
m_progress(this, status, info, percentage);
|
||||
});
|
||||
}
|
||||
if (status == UpdateStatus::Failed || status == UpdateStatus::Finished) {
|
||||
log::info("Deleting InstallTicket at {}", this);
|
||||
// clean up own memory
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
InstallTicket::InstallTicket(
|
||||
Index* index,
|
||||
std::vector<std::string> const& list,
|
||||
ItemInstallCallback progress
|
||||
) : m_index(index),
|
||||
m_installList(list),
|
||||
m_progress(progress) {}
|
||||
|
||||
std::vector<std::string> InstallTicket::getInstallList() const {
|
||||
return m_installList;
|
||||
}
|
||||
|
||||
void InstallTicket::install(std::string const& id) {
|
||||
// run installing in another thread in order
|
||||
// to render progress on screen while installing
|
||||
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
|
||||
|
||||
auto item = m_index->getKnownItem(id);
|
||||
|
||||
this->postProgress(UpdateStatus::Progress, "Checking status", 0);
|
||||
|
||||
// download to temp file in index dir
|
||||
auto tempFile = indexDir / item.m_download.m_filename;
|
||||
|
||||
this->postProgress(UpdateStatus::Progress, "Fetching binary", 0);
|
||||
auto res = web::fetchFile(
|
||||
item.m_download.m_url,
|
||||
tempFile,
|
||||
[this, tempFile](double now, double total) -> int {
|
||||
// check if cancelled
|
||||
std::lock_guard cancelLock(m_cancelMutex);
|
||||
if (m_cancelling) {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
// no need to scope the lock guard more as this
|
||||
// function is going to exit right after anyway
|
||||
|
||||
this->postProgress(
|
||||
UpdateStatus::Progress,
|
||||
"Downloading binary",
|
||||
static_cast<uint8_t>(now / total * 100.0)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
if (!res) {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
return this->postProgress(
|
||||
UpdateStatus::Failed,
|
||||
"Downloading failed: " + res.error()
|
||||
);
|
||||
}
|
||||
|
||||
// check if cancelled
|
||||
{
|
||||
std::lock_guard cancelLock(m_cancelMutex);
|
||||
if (m_cancelling) {
|
||||
ghc::filesystem::remove(tempFile);
|
||||
return;
|
||||
}
|
||||
// scope ends here since we don't need to
|
||||
// access m_cancelling anymore
|
||||
}
|
||||
|
||||
// check for 404
|
||||
auto notFound = utils::file::readString(tempFile);
|
||||
if (notFound && notFound.value() == "Not Found") {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
return this->postProgress(
|
||||
UpdateStatus::Failed,
|
||||
"Binary file download returned \"Not found\". Report "
|
||||
"this to the Geode development team."
|
||||
);
|
||||
}
|
||||
|
||||
// verify checksum
|
||||
this->postProgress(UpdateStatus::Progress, "Verifying", 100);
|
||||
|
||||
auto checksum = ::calculateHash(tempFile.string());
|
||||
|
||||
if (checksum != item.m_download.m_hash) {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
return this->postProgress(
|
||||
UpdateStatus::Failed,
|
||||
"Checksum mismatch! (Downloaded file did not match what "
|
||||
"was expected. Try again, and if the download fails another time, "
|
||||
"report this to the Geode development team."
|
||||
);
|
||||
}
|
||||
|
||||
// move temp file to geode directory
|
||||
try {
|
||||
auto modDir = Loader::get()->getGeodeDirectory() / "mods";
|
||||
auto targetFile = modDir / item.m_download.m_filename;
|
||||
|
||||
// find valid filename that doesn't exist yet
|
||||
if (!m_replaceFiles) {
|
||||
auto filename = ghc::filesystem::path(
|
||||
item.m_download.m_filename
|
||||
).replace_extension("").string();
|
||||
|
||||
size_t number = 0;
|
||||
while (ghc::filesystem::exists(targetFile)) {
|
||||
targetFile = modDir /
|
||||
(filename + std::to_string(number) + ".geode");
|
||||
number++;
|
||||
}
|
||||
}
|
||||
|
||||
// move file
|
||||
ghc::filesystem::rename(tempFile, targetFile);
|
||||
} catch(std::exception& e) {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
return this->postProgress(
|
||||
UpdateStatus::Failed,
|
||||
"Unable to move downloaded file to mods directory: \"" +
|
||||
std::string(e.what()) + " \" "
|
||||
"(This might be due to insufficient permissions to "
|
||||
"write files under SteamLibrary, try running GD as "
|
||||
"administrator)"
|
||||
);
|
||||
}
|
||||
|
||||
// call next in queue or post finish message
|
||||
Loader::get()->queueInGDThread([this, id]() -> void {
|
||||
// todo: Loader::get()->refreshMods(m_updateMod);
|
||||
// where the Loader unloads the mod binary and
|
||||
// reloads it from disk (this should prolly be
|
||||
// done only for the installed mods)
|
||||
Loader::get()->refreshMods();
|
||||
// already in GD thread, so might aswell do the
|
||||
// progress posting manually
|
||||
if (m_progress) {
|
||||
(m_progress)(
|
||||
this, UpdateStatus::Finished, "", 100
|
||||
);
|
||||
}
|
||||
// clean up memory
|
||||
delete this;
|
||||
});
|
||||
}
|
||||
|
||||
void InstallTicket::cancel() {
|
||||
// really no point in using std::lock_guard here
|
||||
// since just plain locking and unlocking the mutex
|
||||
// will do the job just fine with the same legibility
|
||||
m_cancelMutex.lock();
|
||||
m_cancelling = true;
|
||||
m_cancelMutex.unlock();
|
||||
}
|
||||
|
||||
void InstallTicket::start(InstallMode mode) {
|
||||
if (m_installing) return;
|
||||
// make sure we have stuff to install
|
||||
if (!m_installList.size()) {
|
||||
return this->postProgress(
|
||||
UpdateStatus::Failed, "Nothing to install", 0
|
||||
);
|
||||
}
|
||||
m_installing = true;
|
||||
switch (mode) {
|
||||
case InstallMode::Concurrent: {
|
||||
for (auto& id : m_installList) {
|
||||
std::thread(&InstallTicket::install, this, id).detach();
|
||||
}
|
||||
} break;
|
||||
|
||||
case InstallMode::Order: {
|
||||
std::thread([this]() -> void {
|
||||
for (auto& id : m_installList) {
|
||||
this->install(id);
|
||||
}
|
||||
}).detach();
|
||||
} break;
|
||||
}
|
||||
}
|
|
@ -293,7 +293,7 @@ static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
|
|||
<< "Please submit this crash report to its developer ("
|
||||
<< faultyMod->getDeveloper() << ") for assistance.\n";
|
||||
}
|
||||
|
||||
|
||||
// geode info
|
||||
file << "\n== Geode Information ==\n";
|
||||
printGeodeInfo(file);
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#undef min
|
||||
#undef max
|
||||
|
||||
static constexpr const int TAG_CONFIRM_INSTALL = 4;
|
||||
static constexpr const int TAG_CONFIRM_UNINSTALL = 5;
|
||||
static constexpr const int TAG_DELETE_SAVEDATA = 6;
|
||||
|
||||
|
@ -411,6 +410,10 @@ bool ModInfoLayer::init(ModObject* obj, ModListView* list) {
|
|||
);
|
||||
m_installStatus->setVisible(false);
|
||||
m_mainLayer->addChild(m_installStatus);
|
||||
|
||||
if (auto handle = Index::get()->isInstallingItem(m_info.m_id)) {
|
||||
m_installations.push_back(handle);
|
||||
}
|
||||
}
|
||||
|
||||
auto closeSpr = CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png");
|
||||
|
@ -496,10 +499,7 @@ void ModInfoLayer::onRepository(CCObject*) {
|
|||
|
||||
void ModInfoLayer::onInstallMod(CCObject*) {
|
||||
auto ticketRes = Index::get()->installItem(
|
||||
Index::get()->getKnownItem(m_info.m_id),
|
||||
[this](InstallTicket* ticket, UpdateStatus status, std::string const& info, uint8_t progress) -> void {
|
||||
this->modInstallProgress(ticket, status, info, progress);
|
||||
}
|
||||
Index::get()->getKnownItem(m_info.m_id)
|
||||
);
|
||||
if (!ticketRes) {
|
||||
return FLAlertLayer::create(
|
||||
|
@ -508,26 +508,50 @@ void ModInfoLayer::onInstallMod(CCObject*) {
|
|||
"OK"
|
||||
)->show();
|
||||
}
|
||||
m_ticket = ticketRes.value();
|
||||
auto items = ticketRes.value();
|
||||
|
||||
auto layer = FLAlertLayer::create(
|
||||
this,
|
||||
createQuickPopup(
|
||||
"Install",
|
||||
"The following <cb>mods</c> will be installed: " +
|
||||
utils::vector::join(m_ticket->getInstallList(), ",") + ".",
|
||||
"Cancel", "OK", 360.f
|
||||
utils::vector::join(items.toInstall(), ",") + ".",
|
||||
"Cancel", "OK",
|
||||
[this, items](FLAlertLayer*, bool btn2) {
|
||||
if (btn2) {
|
||||
if (m_updateVersionLabel) {
|
||||
m_updateVersionLabel->setVisible(false);
|
||||
}
|
||||
this->updateInstallStatus("Starting install", 0);
|
||||
|
||||
m_installBtn->setTarget(
|
||||
this, menu_selector(ModInfoLayer::onCancelInstall)
|
||||
);
|
||||
m_installBtnSpr->setString("Cancel");
|
||||
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
||||
|
||||
this->retain();
|
||||
|
||||
m_installations = items.begin([this](
|
||||
std::string const& mod,
|
||||
UpdateStatus status,
|
||||
std::string const& value,
|
||||
uint8_t progress
|
||||
) {
|
||||
this->modInstallProgress(mod, status, value, progress);
|
||||
});
|
||||
} else {
|
||||
this->updateInstallStatus("", 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
layer->setTag(TAG_CONFIRM_INSTALL);
|
||||
layer->show();
|
||||
}
|
||||
|
||||
void ModInfoLayer::onCancelInstall(CCObject*) {
|
||||
m_installBtn->setEnabled(false);
|
||||
m_installBtnSpr->setString("Cancelling");
|
||||
|
||||
if (m_ticket) {
|
||||
m_ticket->cancel();
|
||||
for (auto& inst : m_installations) {
|
||||
inst->cancel();
|
||||
}
|
||||
m_installations.clear();
|
||||
if (m_updateVersionLabel) {
|
||||
m_updateVersionLabel->setVisible(true);
|
||||
}
|
||||
|
@ -546,14 +570,6 @@ void ModInfoLayer::onUninstall(CCObject*) {
|
|||
|
||||
void ModInfoLayer::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
|
||||
switch (layer->getTag()) {
|
||||
case TAG_CONFIRM_INSTALL: {
|
||||
if (btn2) {
|
||||
this->install();
|
||||
} else {
|
||||
this->updateInstallStatus("", 0);
|
||||
}
|
||||
} break;
|
||||
|
||||
case TAG_CONFIRM_UNINSTALL: {
|
||||
if (btn2) {
|
||||
this->uninstall();
|
||||
|
@ -596,7 +612,7 @@ void ModInfoLayer::updateInstallStatus(
|
|||
}
|
||||
|
||||
void ModInfoLayer::modInstallProgress(
|
||||
InstallTicket*,
|
||||
std::string const& mod,
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t percentage
|
||||
|
@ -615,10 +631,22 @@ void ModInfoLayer::modInstallProgress(
|
|||
m_installBtnSpr->setString("Install");
|
||||
m_installBtnSpr->setBG("GE_button_01.png"_spr, false);
|
||||
|
||||
m_ticket = nullptr;
|
||||
for (auto& inst : m_installations) {
|
||||
inst->cancel();
|
||||
}
|
||||
m_installations.clear();
|
||||
this->release();
|
||||
} break;
|
||||
|
||||
case UpdateStatus::Finished: {
|
||||
// if some installations are still running, keep going
|
||||
for (auto& inst : m_installations) {
|
||||
if (!inst->finished()) return;
|
||||
}
|
||||
|
||||
// load mods
|
||||
Loader::get()->refreshMods();
|
||||
|
||||
this->updateInstallStatus("", 100);
|
||||
|
||||
FLAlertLayer::create(
|
||||
|
@ -629,10 +657,12 @@ void ModInfoLayer::modInstallProgress(
|
|||
"OK"
|
||||
)->show();
|
||||
|
||||
m_ticket = nullptr;
|
||||
m_installations.clear();
|
||||
|
||||
if (m_list) m_list->refreshList();
|
||||
this->onClose(nullptr);
|
||||
|
||||
this->release();
|
||||
} break;
|
||||
|
||||
default: {
|
||||
|
@ -641,23 +671,6 @@ void ModInfoLayer::modInstallProgress(
|
|||
}
|
||||
}
|
||||
|
||||
void ModInfoLayer::install() {
|
||||
if (m_ticket) {
|
||||
if (m_updateVersionLabel) {
|
||||
m_updateVersionLabel->setVisible(false);
|
||||
}
|
||||
this->updateInstallStatus("Starting install", 0);
|
||||
|
||||
m_installBtn->setTarget(
|
||||
this, menu_selector(ModInfoLayer::onCancelInstall)
|
||||
);
|
||||
m_installBtnSpr->setString("Cancel");
|
||||
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
||||
|
||||
m_ticket->start();
|
||||
}
|
||||
}
|
||||
|
||||
void ModInfoLayer::uninstall() {
|
||||
auto res = m_mod->uninstall();
|
||||
if (!res) {
|
||||
|
@ -732,6 +745,7 @@ void ModInfoLayer::keyDown(enumKeyCodes key) {
|
|||
void ModInfoLayer::onClose(CCObject* pSender) {
|
||||
this->setKeyboardEnabled(false);
|
||||
this->removeFromParentAndCleanup(true);
|
||||
m_installations.clear();
|
||||
};
|
||||
|
||||
ModInfoLayer* ModInfoLayer::create(Mod* mod, ModListView* list) {
|
||||
|
|
|
@ -37,7 +37,7 @@ protected:
|
|||
IconButtonSprite* m_installBtnSpr;
|
||||
CCMenuItemSpriteExtra* m_installBtn;
|
||||
CCLabelBMFont* m_updateVersionLabel = nullptr;
|
||||
InstallTicket* m_ticket = nullptr;
|
||||
InstallHandles m_installations;
|
||||
MDTextArea* m_detailsArea;
|
||||
MDTextArea* m_changelogArea;
|
||||
Scrollbar* m_scrollbar;
|
||||
|
@ -60,7 +60,7 @@ protected:
|
|||
void updateInstallStatus(std::string const& status, uint8_t progress);
|
||||
|
||||
void modInstallProgress(
|
||||
InstallTicket*,
|
||||
std::string const& mod,
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t percentage
|
||||
|
|
|
@ -18,13 +18,14 @@ public:
|
|||
std::string const& content,
|
||||
const char* btn1,
|
||||
const char* btn2,
|
||||
float width,
|
||||
std::function<void(FLAlertLayer*, bool)> selected
|
||||
) {
|
||||
auto inst = new QuickPopup;
|
||||
inst->m_selected = selected;
|
||||
if (inst && inst->init(
|
||||
inst, title, content,
|
||||
btn1, btn2, 350.f, false, .0f
|
||||
btn1, btn2, width, false, .0f
|
||||
)) {
|
||||
inst->autorelease();
|
||||
return inst;
|
||||
|
@ -39,6 +40,7 @@ FLAlertLayer* geode::createQuickPopup(
|
|||
std::string const& content,
|
||||
const char* btn1,
|
||||
const char* btn2,
|
||||
float width,
|
||||
std::function<void(FLAlertLayer*, bool)> selected,
|
||||
bool doShow
|
||||
) {
|
||||
|
@ -46,6 +48,7 @@ FLAlertLayer* geode::createQuickPopup(
|
|||
title,
|
||||
content,
|
||||
btn1, btn2,
|
||||
width,
|
||||
selected
|
||||
);
|
||||
if (doShow) {
|
||||
|
@ -53,3 +56,17 @@ FLAlertLayer* geode::createQuickPopup(
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
FLAlertLayer* geode::createQuickPopup(
|
||||
const char* title,
|
||||
std::string const& content,
|
||||
const char* btn1,
|
||||
const char* btn2,
|
||||
std::function<void(FLAlertLayer*, bool)> selected,
|
||||
bool doShow
|
||||
) {
|
||||
return createQuickPopup(
|
||||
title, content, btn1, btn2, 350.f,
|
||||
selected, doShow
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
#include <Geode/utils/fetch.hpp>
|
||||
#include <curl/curl.h>
|
||||
#include <Geode/utils/casts.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/utils/vector.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
using namespace web;
|
||||
|
||||
namespace geode::utils::fetch {
|
||||
static size_t writeData(char* data, size_t size, size_t nmemb, void* str) {
|
||||
static size_t writeBytes(char* data, size_t size, size_t nmemb, void* str) {
|
||||
as<byte_array*>(str)->insert(
|
||||
as<byte_array*>(str)->end(),
|
||||
data, data + size * nmemb
|
||||
);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
static size_t writeString(char* data, size_t size, size_t nmemb, void* str) {
|
||||
as<std::string*>(str)->append(data, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
static size_t writeBinaryData(char* data, size_t size, size_t nmemb, void* file) {
|
||||
as<std::ofstream*>(file)->write(data, size * nmemb);
|
||||
as<std::ostream*>(file)->write(data, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
|
@ -62,16 +73,16 @@ Result<> web::fetchFile(
|
|||
return Err("Error getting info: " + std::string(curl_easy_strerror(res)));
|
||||
}
|
||||
|
||||
Result<std::string> web::fetch(std::string const& url) {
|
||||
Result<byte_array> web::fetchBytes(std::string const& url) {
|
||||
auto curl = curl_easy_init();
|
||||
|
||||
if (!curl) return Err("Curl not initialized!");
|
||||
|
||||
std::string ret;
|
||||
byte_array ret;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeData);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBytes);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
|
||||
auto res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
|
@ -89,4 +100,301 @@ Result<std::string> web::fetch(std::string const& url) {
|
|||
return Err("Error getting info: " + std::string(curl_easy_strerror(res)));
|
||||
}
|
||||
|
||||
Result<std::string> web::fetch(std::string const& url) {
|
||||
auto curl = curl_easy_init();
|
||||
|
||||
if (!curl) return Err("Curl not initialized!");
|
||||
|
||||
std::string ret;
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeString);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
|
||||
auto res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
curl_easy_cleanup(curl);
|
||||
return Err("Fetch failed");
|
||||
}
|
||||
|
||||
char* ct;
|
||||
res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
|
||||
if ((res == CURLE_OK) && ct) {
|
||||
curl_easy_cleanup(curl);
|
||||
return Ok(ret);
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
return Err("Error getting info: " + std::string(curl_easy_strerror(res)));
|
||||
}
|
||||
|
||||
static std::unordered_map<std::string, SentAsyncWebRequestHandle> RUNNING_REQUESTS {};
|
||||
static std::mutex RUNNING_REQUESTS_MUTEX;
|
||||
|
||||
SentAsyncWebRequest::SentAsyncWebRequest(
|
||||
AsyncWebRequest const& req,
|
||||
std::string const& id
|
||||
) : m_id(id),
|
||||
m_url(req.m_url),
|
||||
m_target(req.m_target)
|
||||
{
|
||||
#define AWAIT_RESUME() \
|
||||
while (m_paused) {}\
|
||||
if (m_cancelled) {\
|
||||
this->doCancel();\
|
||||
return;\
|
||||
}
|
||||
|
||||
if (req.m_then) m_thens.push_back(req.m_then);
|
||||
if (req.m_progress) m_progresses.push_back(req.m_progress);
|
||||
if (req.m_cancelled) m_cancelleds.push_back(req.m_cancelled);
|
||||
if (req.m_expect) m_expects.push_back(req.m_expect);
|
||||
|
||||
std::thread([this]() {
|
||||
|
||||
AWAIT_RESUME();
|
||||
|
||||
auto curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
return this->error("Curl not initialized");
|
||||
}
|
||||
|
||||
// resulting byte array
|
||||
byte_array ret;
|
||||
// output file if downloading to file. unique_ptr because not always
|
||||
// initialized but don't wanna manually managed memory
|
||||
std::unique_ptr<std::ofstream> file = nullptr;
|
||||
|
||||
// into file
|
||||
if (std::holds_alternative<ghc::filesystem::path>(m_target)) {
|
||||
file = std::make_unique<std::ofstream>(
|
||||
std::get<ghc::filesystem::path>(m_target),
|
||||
std::ios::out | std::ios::binary
|
||||
);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, file.get());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData);
|
||||
}
|
||||
// into stream
|
||||
else if (std::holds_alternative<std::ostream*>(m_target)) {
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, std::get<std::ostream*>(m_target));
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData);
|
||||
}
|
||||
// into memory
|
||||
else {
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBytes);
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_URL, m_url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
|
||||
struct ProgressData {
|
||||
SentAsyncWebRequest* self;
|
||||
std::ofstream* file;
|
||||
} data { this, file.get() };
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION,
|
||||
+[](void* ptr, double total, double now, double, double) -> int {
|
||||
auto data = reinterpret_cast<ProgressData*>(ptr);
|
||||
while (data->self->m_paused) {}
|
||||
if (data->self->m_cancelled) {
|
||||
if (data->file) {
|
||||
data->file->close();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
Loader::get()->queueInGDThread([self = data->self, now, total]() {
|
||||
std::lock_guard _(self->m_mutex);
|
||||
for (auto& prog : self->m_progresses) {
|
||||
prog(*self, now, total);
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &data);
|
||||
auto res = curl_easy_perform(curl);
|
||||
|
||||
char* ct;
|
||||
res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct);
|
||||
curl_easy_cleanup(curl);
|
||||
if ((res != CURLE_OK) || !ct) {
|
||||
return this->error("Fetch failed: " + std::string(curl_easy_strerror(res)));
|
||||
}
|
||||
|
||||
AWAIT_RESUME();
|
||||
|
||||
// if something is still holding a handle to this
|
||||
// request, then they may still cancel it
|
||||
m_finished = true;
|
||||
|
||||
Loader::get()->queueInGDThread([this, ret]() {
|
||||
std::lock_guard _(m_mutex);
|
||||
for (auto& then : m_thens) {
|
||||
then(*this, ret);
|
||||
}
|
||||
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
|
||||
RUNNING_REQUESTS.erase(m_id);
|
||||
});
|
||||
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void SentAsyncWebRequest::doCancel() {
|
||||
if (m_cleanedUp) return;
|
||||
m_cleanedUp = true;
|
||||
|
||||
// remove file if downloaded to one
|
||||
if (std::holds_alternative<ghc::filesystem::path>(m_target)) {
|
||||
auto path = std::get<ghc::filesystem::path>(m_target);
|
||||
if (ghc::filesystem::exists(path)) {
|
||||
try {
|
||||
ghc::filesystem::remove(path);
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
Loader::get()->queueInGDThread([this]() {
|
||||
std::lock_guard _(m_mutex);
|
||||
for (auto& canc : m_cancelleds) {
|
||||
canc(*this);
|
||||
}
|
||||
});
|
||||
|
||||
this->error("Request cancelled");
|
||||
}
|
||||
|
||||
void SentAsyncWebRequest::cancel() {
|
||||
m_cancelled = true;
|
||||
// if already finished, cancel anyway to clean up
|
||||
if (m_finished) {
|
||||
this->doCancel();
|
||||
}
|
||||
}
|
||||
|
||||
void SentAsyncWebRequest::pause() {
|
||||
m_paused = true;
|
||||
}
|
||||
|
||||
void SentAsyncWebRequest::resume() {
|
||||
m_paused = false;
|
||||
}
|
||||
|
||||
bool SentAsyncWebRequest::finished() const {
|
||||
return m_finished;
|
||||
}
|
||||
|
||||
void SentAsyncWebRequest::error(std::string const& error) {
|
||||
while (m_paused) {};
|
||||
Loader::get()->queueInGDThread([this, error]() {
|
||||
std::lock_guard _(m_mutex);
|
||||
for (auto& expect : m_expects) {
|
||||
expect(error);
|
||||
}
|
||||
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
|
||||
RUNNING_REQUESTS.erase(m_id);
|
||||
});
|
||||
}
|
||||
|
||||
AsyncWebRequest& AsyncWebRequest::join(std::string const& requestID) {
|
||||
m_joinID = requestID;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncWebResponse AsyncWebRequest::fetch(std::string const& url) {
|
||||
m_url = url;
|
||||
return AsyncWebResponse(*this);
|
||||
}
|
||||
|
||||
AsyncWebRequest& AsyncWebRequest::expect(std::function<void(std::string const&)> handler) {
|
||||
m_expect = handler;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncWebRequest& AsyncWebRequest::progress(AsyncProgress progress) {
|
||||
m_progress = progress;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AsyncWebRequest& AsyncWebRequest::cancelled(AsyncCancelled cancelledFunc) {
|
||||
m_cancelled = cancelledFunc;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SentAsyncWebRequestHandle AsyncWebRequest::send() {
|
||||
if (m_sent) return nullptr;
|
||||
m_sent = true;
|
||||
|
||||
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
|
||||
|
||||
// pause all running requests
|
||||
for (auto& [_, req] : RUNNING_REQUESTS) {
|
||||
req->pause();
|
||||
}
|
||||
|
||||
SentAsyncWebRequestHandle ret;
|
||||
|
||||
static size_t COUNTER = 0;
|
||||
if (m_joinID && RUNNING_REQUESTS.count(m_joinID.value())) {
|
||||
auto& req = RUNNING_REQUESTS.at(m_joinID.value());
|
||||
std::lock_guard _(req->m_mutex);
|
||||
if (m_then) req->m_thens.push_back(m_then);
|
||||
if (m_progress) req->m_progresses.push_back(m_progress);
|
||||
if (m_expect) req->m_expects.push_back(m_expect);
|
||||
if (m_cancelled) req->m_cancelleds.push_back(m_cancelled);
|
||||
ret = req;
|
||||
} else {
|
||||
auto id = m_joinID.value_or("__anon_request_" + std::to_string(COUNTER++));
|
||||
ret = std::make_shared<SentAsyncWebRequest>(*this, id);
|
||||
RUNNING_REQUESTS.insert({ id, ret });
|
||||
}
|
||||
|
||||
// resume all running requests
|
||||
for (auto& [_, req] : RUNNING_REQUESTS) {
|
||||
req->resume();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AsyncWebRequest::~AsyncWebRequest() {
|
||||
this->send();
|
||||
}
|
||||
|
||||
AsyncWebResult<std::monostate> AsyncWebResponse::into(std::ostream& stream) {
|
||||
m_request.m_target = &stream;
|
||||
return this->as(+[](byte_array const&) {
|
||||
return Ok(std::monostate());
|
||||
});
|
||||
}
|
||||
|
||||
AsyncWebResult<std::monostate> AsyncWebResponse::into(ghc::filesystem::path const& path) {
|
||||
m_request.m_target = path;
|
||||
return this->as(+[](byte_array const&) {
|
||||
return Ok(std::monostate());
|
||||
});
|
||||
}
|
||||
|
||||
AsyncWebResult<std::string> AsyncWebResponse::text() {
|
||||
return this->as(+[](byte_array const& bytes) {
|
||||
return Ok(std::string(bytes.begin(), bytes.end()));
|
||||
});
|
||||
}
|
||||
|
||||
AsyncWebResult<byte_array> AsyncWebResponse::bytes() {
|
||||
return this->as(+[](byte_array const& bytes) {
|
||||
return Ok(bytes);
|
||||
});
|
||||
}
|
||||
|
||||
AsyncWebResult<nlohmann::json> AsyncWebResponse::json() {
|
||||
return this->as(+[](byte_array const& bytes) -> Result<nlohmann::json> {
|
||||
try {
|
||||
return Ok(nlohmann::json::parse(bytes.begin(), bytes.end()));
|
||||
} catch(std::exception& e) {
|
||||
return Err(std::string(e.what()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue