add support for supersede updates from server ()

This commit is contained in:
Fleeym 2024-06-03 18:12:48 +03:00 committed by GitHub
parent e6e1d34694
commit b96be1a8f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 94 additions and 15 deletions

View file

@ -1,4 +1,6 @@
#include "DownloadManager.hpp"
#include "Geode/loader/Mod.hpp"
#include <optional>
using namespace server;
@ -18,6 +20,7 @@ public:
std::string m_id;
std::optional<VersionInfo> m_version;
std::optional<DependencyFor> m_dependencyFor;
std::optional<std::string> m_replacesMod;
DownloadStatus m_status;
EventListener<ServerRequest<ServerModVersion>> m_infoListener;
EventListener<web::WebTask> m_downloadListener;
@ -25,11 +28,13 @@ public:
Impl(
std::string const& id,
std::optional<VersionInfo> const& version,
std::optional<DependencyFor> const& dependencyFor
std::optional<DependencyFor> const& dependencyFor,
std::optional<std::string> const& replacesMod
)
: m_id(id),
m_version(version),
m_dependencyFor(dependencyFor),
m_replacesMod(replacesMod),
m_status(DownloadStatusFetching {
.percentage = 0,
})
@ -97,8 +102,9 @@ public:
if (auto value = event->getValue()) {
if (value->ok()) {
bool removingInstalledWasError = false;
// If this was an update, delete the old file first
if (auto mod = Loader::get()->getInstalledMod(m_id)) {
bool disablingReplacedWasError = false;
std::string id = m_replacesMod.has_value() ? m_replacesMod.value() : m_id;
if (auto mod = Loader::get()->getInstalledMod(id)) {
std::error_code ec;
std::filesystem::remove(mod->getPackagePath(), ec);
if (ec) {
@ -108,6 +114,7 @@ public:
};
}
}
// If this was an update, delete the old file first
if (!removingInstalledWasError) {
auto ok = file::writeBinary(dirs::getModsDir() / (m_id + ".geode"), value->data());
if (!ok) {
@ -146,8 +153,9 @@ public:
ModDownload::ModDownload(
std::string const& id,
std::optional<VersionInfo> const& version,
std::optional<DependencyFor> const& dependencyFor
) : m_impl(std::make_shared<Impl>(id, version, dependencyFor)) {}
std::optional<DependencyFor> const& dependencyFor,
std::optional<std::string> const& replacesMod
) : m_impl(std::make_shared<Impl>(id, version, dependencyFor, replacesMod)) {}
void ModDownload::confirm() {
m_impl->confirm();
@ -156,6 +164,9 @@ void ModDownload::confirm() {
std::optional<DependencyFor> ModDownload::getDependencyFor() const {
return m_impl->m_dependencyFor;
}
std::optional<std::string> ModDownload::getReplacesMod() const {
return m_impl->m_replacesMod;
}
bool ModDownload::isDone() const {
return std::holds_alternative<DownloadStatusDone>(m_impl->m_status);
}
@ -225,7 +236,8 @@ void ModDownload::cancel() {
std::optional<ModDownload> ModDownloadManager::startDownload(
std::string const& id,
std::optional<VersionInfo> const& version,
std::optional<DependencyFor> const& dependencyFor
std::optional<DependencyFor> const& dependencyFor,
std::optional<std::string> const& replacesMod
) {
// If this mod has already been succesfully downloaded or is currently
// being downloaded, return as you can't download multiple versions of the
@ -242,7 +254,7 @@ std::optional<ModDownload> ModDownloadManager::startDownload(
// Start a new download by constructing a ModDownload (which starts the
// download)
m_impl->m_downloads.emplace(id, ModDownload(id, version, dependencyFor));
m_impl->m_downloads.emplace(id, ModDownload(id, version, dependencyFor, replacesMod));
return m_impl->m_downloads.at(id);
}
void ModDownloadManager::cancelAll() {
@ -261,7 +273,16 @@ void ModDownloadManager::startUpdateAll() {
if (result->isOk()) {
for (auto& mod : result->unwrap()) {
if (mod.hasUpdateForInstalledMod()) {
this->startDownload(mod.id, std::nullopt);
if (mod.replacement.has_value()) {
this->startDownload(
mod.replacement.value().id,
mod.replacement.value().version,
std::nullopt,
mod.id
);
} else {
this->startDownload(mod.id, std::nullopt);
}
}
}
}

View file

@ -65,7 +65,8 @@ namespace server {
ModDownload(
std::string const& id,
std::optional<VersionInfo> const& version,
std::optional<DependencyFor> const& dependencyFor
std::optional<DependencyFor> const& dependencyFor,
std::optional<std::string> const& replacesMod
);
friend class ModDownloadManager;
@ -77,6 +78,7 @@ namespace server {
bool isDone() const;
bool isActive() const;
bool canRetry() const;
std::optional<std::string> getReplacesMod() const;
std::optional<DependencyFor> getDependencyFor() const;
std::string getID() const;
DownloadStatus getStatus() const;
@ -100,7 +102,8 @@ namespace server {
std::optional<ModDownload> startDownload(
std::string const& id,
std::optional<VersionInfo> const& version,
std::optional<DependencyFor> const& dependencyFor = std::nullopt
std::optional<DependencyFor> const& dependencyFor = std::nullopt,
std::optional<std::string> const& replacesMod = std::nullopt
);
void startUpdateAll();
void confirmAll();

View file

@ -348,6 +348,23 @@ Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) {
return Ok(res);
}
Result<ServerModReplacement> ServerModReplacement::parse(matjson::Value const& raw) {
log::info("entered replacement parse");
auto json = raw;
log::info("{}", json);
JsonChecker checker(json);
auto root = checker.root("ServerModReplacement").obj();
auto res = ServerModReplacement();
root.needs("id").into(res.id);
root.needs("version").into(res.version);
if (root.isError()) {
return Err(root.getError());
}
return Ok(res);
}
Result<ServerModUpdate> ServerModUpdate::parse(matjson::Value const& raw) {
auto json = raw;
JsonChecker checker(json);
@ -357,6 +374,9 @@ Result<ServerModUpdate> ServerModUpdate::parse(matjson::Value const& raw) {
root.needs("id").into(res.id);
root.needs("version").into(res.version);
if (root.has("replacement")) {
GEODE_UNWRAP_INTO(res.replacement, ServerModReplacement::parse(root.has("replacement").json()));
}
// Check for errors and return result
if (root.isError()) {
@ -390,7 +410,7 @@ Result<std::vector<ServerModUpdate>> ServerModUpdate::parseList(matjson::Value c
bool ServerModUpdate::hasUpdateForInstalledMod() const {
if (auto mod = Loader::get()->getInstalledMod(this->id)) {
return mod->getVersion() < this->version;
return mod->getVersion() < this->version || this->replacement.has_value();
}
return false;
}
@ -739,7 +759,10 @@ ServerRequest<std::optional<ServerModUpdate>> server::checkUpdates(Mod* mod) {
[mod](Result<std::vector<ServerModUpdate>, ServerError>* result) -> Result<std::optional<ServerModUpdate>, ServerError> {
if (result->isOk()) {
for (auto& update : result->unwrap()) {
if (update.id == mod->getID() && update.version > mod->getVersion()) {
if (
update.id == mod->getID() &&
(update.version > mod->getVersion() || update.replacement.has_value())
) {
return Ok(update);
}
}

View file

@ -1,8 +1,11 @@
#pragma once
#include "Geode/utils/VersionInfo.hpp"
#include <Geode/DefaultInclude.hpp>
#include <Geode/utils/web2.hpp>
#include <Geode/loader/SettingEvent.hpp>
#include <matjson.hpp>
#include <vector>
using namespace geode::prelude;
@ -34,10 +37,19 @@ namespace server {
static Result<ServerModVersion> parse(matjson::Value const& json);
};
struct ServerModReplacement final {
std::string id;
VersionInfo version;
std::string download_link;
static Result<ServerModReplacement> parse(matjson::Value const& json);
};
struct ServerModUpdate final {
std::string id;
VersionInfo version;
std::optional<ServerModReplacement> replacement;
static Result<ServerModUpdate> parse(matjson::Value const& json);
static Result<std::vector<ServerModUpdate>> parseList(matjson::Value const& json);

View file

@ -325,7 +325,12 @@ void ModItem::updateState() {
update && !(download && (download->isActive() || download->isDone()))
) {
m_updateBtn->setVisible(true);
auto updateString = m_source.getMetadata().getVersion().toString() + " -> " + update->version.toString();
std::string updateString = "";
if (update->replacement.has_value()) {
updateString += " -> " + update->replacement.value().id;
} else {
updateString += m_source.getMetadata().getVersion().toVString() + " -> " + update->version.toVString();
}
m_versionLabel->setString(updateString.c_str());
m_versionLabel->setColor(to3B(ColorProvider::get()->color("mod-list-version-label-updates-available"_spr)));
@ -439,6 +444,17 @@ void ModItem::onEnable(CCObject*) {
UpdateModListStateEvent(UpdateModState(m_source.getID())).post();
}
void ModItem::onInstall(CCObject*) {
if (auto updates = m_source.hasUpdates()) {
if (updates->replacement.has_value()) {
server::ModDownloadManager::get()->startDownload(
updates->replacement->id,
updates->replacement->version,
std::nullopt,
m_source.getID()
);
return;
}
}
server::ModDownloadManager::get()->startDownload(m_source.getID(), std::nullopt);
}
void ModItem::onDevelopers(CCObject*) {

View file

@ -7,6 +7,7 @@ using namespace server;
void askConfirmModInstalls() {
struct ToConfirm final {
size_t modCount = 0;
size_t replacementCount = 0;
size_t dependencyCount = 0;
std::unordered_set<Mod*> toDisable;
std::unordered_set<Mod*> toEnable;
@ -23,6 +24,9 @@ void askConfirmModInstalls() {
}
else {
toConfirm.modCount += 1;
if (download.getReplacesMod()) {
toConfirm.replacementCount += 1;
}
// Since the user has already explicitly chosen to download these mods, we
// are going to assume they want these mods enabled over already installed
@ -68,10 +72,10 @@ void askConfirmModInstalls() {
createQuickPopup(
"Confirm Install",
fmt::format(
"<cj>{}</c> mods will be installed, of which <cy>{}</c> are <cy>dependencies</c>.\n"
"<cj>{}</c> mods will be installed, of which <cy>{}</c> are <cy>dependencies</c> and <cy>{}</c> are <cy>replacements</c>.\n"
"<cr>{} mods will be force-disabled, as they are incompatible</c>: {}\n"
"<cg>{} mods will be force-enabled</c>: {}",
toConfirm.modCount, toConfirm.dependencyCount,
toConfirm.modCount, toConfirm.dependencyCount, toConfirm.replacementCount,
toConfirm.toDisable.size(), joinModsToIDs(toConfirm.toDisable),
toConfirm.toEnable.size(), joinModsToIDs(toConfirm.toEnable)
),