mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-24 03:39:56 -04:00
add support for supersede updates from server (#810)
This commit is contained in:
parent
e6e1d34694
commit
b96be1a8f2
6 changed files with 94 additions and 15 deletions
loader/src
server
ui/mods
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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*) {
|
||||
|
|
|
@ -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)
|
||||
),
|
||||
|
|
Loading…
Add table
Reference in a new issue