mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-26 04:39:51 -04:00
installing & updating mods (needs work still)
This commit is contained in:
parent
5c3111e564
commit
684a109dee
21 changed files with 1324 additions and 214 deletions
loader
include/Geode/utils
src
|
@ -112,3 +112,16 @@ namespace geode {
|
|||
cocos2d::ccColor3B color3b(std::string const& id) const;
|
||||
};
|
||||
}
|
||||
|
||||
GEODE_HIDDEN inline cocos2d::ccColor4B operator"" _cc4b_gd(const char* str, size_t) {
|
||||
return geode::ColorProvider::get()->color(str);
|
||||
}
|
||||
GEODE_HIDDEN inline cocos2d::ccColor3B operator"" _cc3b_gd(const char* str, size_t) {
|
||||
return geode::ColorProvider::get()->color3b(str);
|
||||
}
|
||||
GEODE_HIDDEN inline cocos2d::ccColor4B operator"" _cc4b(const char* str, size_t) {
|
||||
return geode::ColorProvider::get()->color(geode::Mod::get()->expandSpriteName(str));
|
||||
}
|
||||
GEODE_HIDDEN inline cocos2d::ccColor3B operator"" _cc3b(const char* str, size_t) {
|
||||
return geode::ColorProvider::get()->color3b(geode::Mod::get()->expandSpriteName(str));
|
||||
}
|
||||
|
|
|
@ -238,6 +238,10 @@ namespace geode {
|
|||
}
|
||||
}
|
||||
|
||||
constexpr VersionInfo getUnderlyingVersion() const {
|
||||
return m_version;
|
||||
}
|
||||
|
||||
std::string toString() const;
|
||||
friend GEODE_DLL std::string format_as(ComparableVersionInfo const& version);
|
||||
};
|
||||
|
|
|
@ -33,26 +33,22 @@ namespace cocos2d {
|
|||
pos.y *= mul;
|
||||
return pos;
|
||||
}
|
||||
|
||||
static cocos2d::CCSize& operator*=(cocos2d::CCSize& size, float mul) {
|
||||
size.width *= mul;
|
||||
size.height *= mul;
|
||||
return size;
|
||||
}
|
||||
|
||||
static cocos2d::CCSize operator*(cocos2d::CCSize const& size, cocos2d::CCPoint const& point) {
|
||||
return {
|
||||
size.width * point.x,
|
||||
size.height * point.y,
|
||||
};
|
||||
}
|
||||
|
||||
static cocos2d::CCRect operator*=(cocos2d::CCRect& rect, float mul) {
|
||||
rect.origin *= mul;
|
||||
rect.size *= mul;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static cocos2d::CCRect operator*(cocos2d::CCRect const& rect, float mul) {
|
||||
return {
|
||||
rect.origin.x * mul,
|
||||
|
@ -61,147 +57,130 @@ namespace cocos2d {
|
|||
rect.size.height * mul,
|
||||
};
|
||||
}
|
||||
|
||||
static cocos2d::CCPoint operator/=(cocos2d::CCPoint& pos, float div) {
|
||||
pos.x /= div;
|
||||
pos.y /= div;
|
||||
return pos;
|
||||
}
|
||||
|
||||
static cocos2d::CCSize operator/=(cocos2d::CCSize& size, float div) {
|
||||
size.width /= div;
|
||||
size.height /= div;
|
||||
return size;
|
||||
}
|
||||
|
||||
static cocos2d::CCRect operator/=(cocos2d::CCRect& rect, float div) {
|
||||
rect.origin /= div;
|
||||
rect.size /= div;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static cocos2d::CCPoint operator+=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) {
|
||||
pos.x += add.x;
|
||||
pos.y += add.y;
|
||||
return pos;
|
||||
}
|
||||
|
||||
static cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) {
|
||||
size.width += add.x;
|
||||
size.height += add.y;
|
||||
return size;
|
||||
}
|
||||
|
||||
static cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCSize const& add) {
|
||||
size.width += add.width;
|
||||
size.height += add.height;
|
||||
return size;
|
||||
}
|
||||
|
||||
static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) {
|
||||
rect.origin += add;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) {
|
||||
rect.size += add;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) {
|
||||
rect.origin += add.origin;
|
||||
rect.size += add.size;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static cocos2d::CCPoint operator-=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) {
|
||||
pos.x -= add.x;
|
||||
pos.y -= add.y;
|
||||
return pos;
|
||||
}
|
||||
|
||||
static cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) {
|
||||
size.width -= add.x;
|
||||
size.height -= add.y;
|
||||
return size;
|
||||
}
|
||||
|
||||
static cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCSize const& add) {
|
||||
size.width -= add.width;
|
||||
size.height -= add.height;
|
||||
return size;
|
||||
}
|
||||
|
||||
static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) {
|
||||
rect.origin -= add;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) {
|
||||
rect.size -= add;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) {
|
||||
rect.origin -= add.origin;
|
||||
rect.size -= add.size;
|
||||
return rect;
|
||||
}
|
||||
|
||||
static cocos2d::CCSize operator-(cocos2d::CCSize const& size, float f) {
|
||||
return {size.width - f, size.height - f};
|
||||
}
|
||||
|
||||
static cocos2d::CCSize operator-(cocos2d::CCSize const& size) {
|
||||
return {-size.width, -size.height};
|
||||
}
|
||||
|
||||
static bool operator==(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) {
|
||||
return p1.x == p2.x && p1.y == p2.y;
|
||||
}
|
||||
|
||||
static bool operator!=(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) {
|
||||
return p1.x != p2.x || p1.y != p2.y;
|
||||
}
|
||||
|
||||
static bool operator==(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
|
||||
return s1.width == s2.width && s1.height == s2.height;
|
||||
}
|
||||
|
||||
static bool operator!=(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
|
||||
return s1.width != s2.width || s1.height != s2.height;
|
||||
}
|
||||
|
||||
static bool operator<(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
|
||||
return s1.width < s2.width || s1.height < s2.height;
|
||||
}
|
||||
static bool operator<=(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
|
||||
return s1.width <= s2.width || s1.height <= s2.height;
|
||||
}
|
||||
static bool operator>(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
|
||||
return s1.width > s2.width || s1.height > s2.height;
|
||||
}
|
||||
static bool operator>=(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
|
||||
return s1.width >= s2.width || s1.height >= s2.height;
|
||||
}
|
||||
static bool operator==(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) {
|
||||
return r1.origin == r2.origin && r1.size == r2.size;
|
||||
}
|
||||
|
||||
static bool operator!=(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) {
|
||||
return r1.origin != r2.origin || r1.size != r2.size;
|
||||
}
|
||||
|
||||
static bool operator==(cocos2d::ccColor4B const& c1, cocos2d::ccColor4B const& c2) {
|
||||
return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b && c1.a == c2.a;
|
||||
}
|
||||
|
||||
static bool operator!=(cocos2d::ccColor4B const& c1, cocos2d::ccColor4B const& c2) {
|
||||
return c1.r != c2.r || c1.g != c2.g || c1.b != c2.b || c1.a != c2.a;
|
||||
}
|
||||
|
||||
static bool operator==(cocos2d::ccColor3B const& c1, cocos2d::ccColor3B const& c2) {
|
||||
return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b;
|
||||
}
|
||||
|
||||
static bool operator!=(cocos2d::ccColor3B const& c1, cocos2d::ccColor3B const& c2) {
|
||||
return c1.r != c2.r || c1.g != c2.g || c1.b != c2.b;
|
||||
}
|
||||
|
||||
static bool operator==(cocos2d::ccHSVValue const& c1, cocos2d::ccHSVValue const& c2) {
|
||||
return c1.h == c2.h && c1.s == c2.s && c1.v == c2.v &&
|
||||
c1.absoluteSaturation == c2.absoluteSaturation &&
|
||||
c1.absoluteBrightness == c2.absoluteBrightness;
|
||||
}
|
||||
|
||||
static bool operator!=(cocos2d::ccHSVValue const& c1, cocos2d::ccHSVValue const& c2) {
|
||||
return !(c1 == c2);
|
||||
}
|
||||
|
|
346
loader/src/server/DownloadManager.cpp
Normal file
346
loader/src/server/DownloadManager.cpp
Normal file
|
@ -0,0 +1,346 @@
|
|||
#include "DownloadManager.hpp"
|
||||
|
||||
using namespace server;
|
||||
|
||||
ModDownloadEvent::ModDownloadEvent(std::string const& id) : id(id) {}
|
||||
|
||||
ListenerResult ModDownloadFilter::handle(MiniFunction<Callback> fn, ModDownloadEvent* event) {
|
||||
if (m_id.empty() || m_id == event->id) {
|
||||
fn(event);
|
||||
}
|
||||
return ListenerResult::Propagate;
|
||||
}
|
||||
ModDownloadFilter::ModDownloadFilter() {}
|
||||
ModDownloadFilter::ModDownloadFilter(std::string const& id) : m_id(id) {}
|
||||
|
||||
class ModDownload::Impl final {
|
||||
public:
|
||||
std::string m_id;
|
||||
std::optional<VersionInfo> m_version;
|
||||
std::optional<std::string> m_dependencyFor;
|
||||
DownloadStatus m_status;
|
||||
EventListener<ServerRequest<ServerModVersion>> m_infoListener;
|
||||
EventListener<web::WebTask> m_downloadListener;
|
||||
|
||||
Impl(
|
||||
std::string const& id,
|
||||
std::optional<VersionInfo> const& version,
|
||||
std::optional<std::string> const& dependencyFor
|
||||
)
|
||||
: m_id(id),
|
||||
m_version(version),
|
||||
m_dependencyFor(dependencyFor),
|
||||
m_status(DownloadStatusFetching {
|
||||
.percentage = 0,
|
||||
})
|
||||
{
|
||||
m_infoListener.bind([this](ServerRequest<ServerModVersion>::Event* event) {
|
||||
if (auto result = event->getValue()) {
|
||||
if (result->isOk()) {
|
||||
auto data = result->unwrap();
|
||||
m_version = data.metadata.getVersion();
|
||||
|
||||
// Start downloads for any missing dependencies
|
||||
for (auto dep : data.metadata.getDependencies()) {
|
||||
if (!dep.mod) {
|
||||
ModDownloadManager::get()->startDownload(
|
||||
dep.id, dep.version.getUnderlyingVersion(), m_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
m_status = DownloadStatusConfirm {
|
||||
.version = data,
|
||||
};
|
||||
}
|
||||
else {
|
||||
m_status = DownloadStatusError {
|
||||
.details = result->unwrapErr().details,
|
||||
};
|
||||
}
|
||||
|
||||
// Clear the listener to free up the memory
|
||||
m_infoListener.setFilter(ServerRequest<ServerModVersion>());
|
||||
}
|
||||
else if (auto progress = event->getProgress()) {
|
||||
m_status = DownloadStatusFetching {
|
||||
.percentage = progress->percentage.value_or(0),
|
||||
};
|
||||
}
|
||||
else if (event->isCancelled()) {
|
||||
m_status = DownloadStatusCancelled();
|
||||
m_infoListener.setFilter(ServerRequest<ServerModVersion>());
|
||||
}
|
||||
|
||||
if (!ModDownloadManager::get()->checkAutoConfirm()) {
|
||||
ModDownloadEvent(m_id).post();
|
||||
}
|
||||
});
|
||||
m_infoListener.setFilter(getModVersion(id, version));
|
||||
Loader::get()->queueInMainThread([id = m_id] {
|
||||
ModDownloadEvent(id).post();
|
||||
});
|
||||
}
|
||||
|
||||
void confirm() {
|
||||
auto confirm = std::get_if<DownloadStatusConfirm>(&m_status);
|
||||
if (!confirm) return;
|
||||
|
||||
auto version = confirm->version;
|
||||
m_status = DownloadStatusDownloading {
|
||||
.percentage = 0,
|
||||
};
|
||||
|
||||
m_downloadListener.bind([this, hash = version.hash](web::WebTask::Event* event) {
|
||||
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)) {
|
||||
std::error_code ec;
|
||||
ghc::filesystem::remove(mod->getPackagePath(), ec);
|
||||
if (ec) {
|
||||
removingInstalledWasError = true;
|
||||
m_status = DownloadStatusError {
|
||||
.details = fmt::format("Unable to delete existing .geode package (code {})", ec),
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!removingInstalledWasError) {
|
||||
auto ok = file::writeBinary(dirs::getModsDir() / (m_id + ".geode"), value->data());
|
||||
if (!ok) {
|
||||
m_status = DownloadStatusError {
|
||||
.details = ok.unwrapErr(),
|
||||
};
|
||||
}
|
||||
else {
|
||||
m_status = DownloadStatusDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_status = DownloadStatusError {
|
||||
.details = value->string().unwrapOr("Unknown error"),
|
||||
};
|
||||
}
|
||||
}
|
||||
else if (auto progress = event->getProgress()) {
|
||||
m_status = DownloadStatusDownloading {
|
||||
.percentage = static_cast<uint8_t>(progress->downloadProgress().value_or(0)),
|
||||
};
|
||||
}
|
||||
else if (event->isCancelled()) {
|
||||
m_status = DownloadStatusCancelled();
|
||||
}
|
||||
ModDownloadEvent(m_id).post();
|
||||
});
|
||||
|
||||
auto req = web::WebRequest();
|
||||
m_downloadListener.setFilter(req.get(version.downloadURL));
|
||||
ModDownloadEvent(m_id).post();
|
||||
}
|
||||
};
|
||||
|
||||
ModDownload::ModDownload(
|
||||
std::string const& id,
|
||||
std::optional<VersionInfo> const& version,
|
||||
std::optional<std::string> const& dependencyFor
|
||||
) : m_impl(std::make_shared<Impl>(id, version, dependencyFor)) {}
|
||||
|
||||
void ModDownload::confirm() {
|
||||
m_impl->confirm();
|
||||
}
|
||||
|
||||
std::optional<std::string> ModDownload::getDependencyFor() const {
|
||||
return m_impl->m_dependencyFor;
|
||||
}
|
||||
bool ModDownload::isDone() const {
|
||||
return std::holds_alternative<DownloadStatusDone>(m_impl->m_status);
|
||||
}
|
||||
bool ModDownload::isActive() const {
|
||||
return !(
|
||||
std::holds_alternative<DownloadStatusDone>(m_impl->m_status) ||
|
||||
std::holds_alternative<DownloadStatusError>(m_impl->m_status) ||
|
||||
std::holds_alternative<DownloadStatusCancelled>(m_impl->m_status)
|
||||
);
|
||||
}
|
||||
bool ModDownload::canRetry() const {
|
||||
return
|
||||
std::holds_alternative<DownloadStatusError>(m_impl->m_status) ||
|
||||
std::holds_alternative<DownloadStatusCancelled>(m_impl->m_status);
|
||||
}
|
||||
std::string ModDownload::getID() const {
|
||||
return m_impl->m_id;
|
||||
}
|
||||
DownloadStatus ModDownload::getStatus() const {
|
||||
return m_impl->m_status;
|
||||
}
|
||||
std::optional<VersionInfo> ModDownload::getVersion() const {
|
||||
return m_impl->m_version;
|
||||
}
|
||||
|
||||
class ModDownloadManager::Impl {
|
||||
public:
|
||||
std::unordered_map<std::string, ModDownload> m_downloads;
|
||||
Task<std::monostate> m_updateAllTask;
|
||||
|
||||
void cancelOrphanedDependencies() {
|
||||
// "This doesn't handle circular dependencies!!!!"
|
||||
// Well OK and the human skull doesn't handle the 5000 newtons
|
||||
// of force from this anvil I'm about to drop on your head
|
||||
|
||||
for (auto& [_, d] : m_downloads) {
|
||||
if (auto depFor = d.m_impl->m_dependencyFor) {
|
||||
if (
|
||||
!m_downloads.contains(*depFor) ||
|
||||
!(m_downloads.at(*depFor).isActive() || m_downloads.at(*depFor).isDone())
|
||||
) {
|
||||
// d.cancel() will cause cancelOrphanedDependencies() to be called again
|
||||
// We want that anyway because cancelling one dependency might cause
|
||||
// dependencies down the chain to become orphaned
|
||||
return d.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void ModDownload::cancel() {
|
||||
if (!std::holds_alternative<DownloadStatusDone>(m_impl->m_status)) {
|
||||
m_impl->m_status = DownloadStatusCancelled();
|
||||
m_impl->m_infoListener.getFilter().cancel();
|
||||
m_impl->m_infoListener.setFilter(ServerRequest<ServerModVersion>());
|
||||
|
||||
// Cancel any dependencies of this mod left over (unless some other
|
||||
// installation depends on them still)
|
||||
ModDownloadManager::get()->m_impl->cancelOrphanedDependencies();
|
||||
ModDownloadEvent(m_impl->m_id).post();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ModDownload> ModDownloadManager::startDownload(
|
||||
std::string const& id,
|
||||
std::optional<VersionInfo> const& version,
|
||||
std::optional<std::string> const& dependencyFor
|
||||
) {
|
||||
// If this mod has already been succesfully downloaded or is currently
|
||||
// being downloaded, return as you can't download multiple versions of the
|
||||
// same mod simultaniously, since that wouldn't make sense. I mean the new
|
||||
// version would just immediately override to the other one
|
||||
if (m_impl->m_downloads.contains(id)) {
|
||||
// If the download errored last time, then we can try again
|
||||
if (m_impl->m_downloads.at(id).canRetry()) {
|
||||
m_impl->m_downloads.erase(id);
|
||||
}
|
||||
// Otherwise return
|
||||
else return std::nullopt;
|
||||
}
|
||||
|
||||
// Start a new download by constructing a ModDownload (which starts the
|
||||
// download)
|
||||
m_impl->m_downloads.emplace(id, ModDownload(id, version, dependencyFor));
|
||||
return m_impl->m_downloads.at(id);
|
||||
}
|
||||
void ModDownloadManager::cancelAll() {
|
||||
for (auto& [_, d] : m_impl->m_downloads) {
|
||||
d.cancel();
|
||||
}
|
||||
}
|
||||
void ModDownloadManager::confirmAll() {
|
||||
for (auto& [_, d] : m_impl->m_downloads) {
|
||||
d.confirm();
|
||||
}
|
||||
}
|
||||
void ModDownloadManager::startUpdateAll() {
|
||||
m_impl->m_updateAllTask = checkAllUpdates().map(
|
||||
[this](Result<std::vector<ServerModUpdate>, ServerError>* result) {
|
||||
if (result->isOk()) {
|
||||
for (auto& mod : result->unwrap()) {
|
||||
if (mod.hasUpdateForInstalledMod()) {
|
||||
this->startDownload(mod.id, std::nullopt);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::monostate();
|
||||
},
|
||||
[](auto) { return std::monostate(); }
|
||||
);
|
||||
}
|
||||
void ModDownloadManager::dismissAll() {
|
||||
std::erase_if(m_impl->m_downloads, [](auto const& d) {
|
||||
return d.second.canRetry();
|
||||
});
|
||||
ModDownloadEvent("").post();
|
||||
}
|
||||
bool ModDownloadManager::checkAutoConfirm() {
|
||||
for (auto& [_, download] : m_impl->m_downloads) {
|
||||
auto status = download.getStatus();
|
||||
if (auto confirm = std::get_if<server::DownloadStatusConfirm>(&status)) {
|
||||
for (auto dep : confirm->version.metadata.getDependencies()) {
|
||||
// If some mod has an optional dependency that isn't installed,
|
||||
// we need to ask for confirmation
|
||||
if (!dep.mod && dep.importance != ModMetadata::Dependency::Importance::Required) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto inc : confirm->version.metadata.getIncompatibilities()) {
|
||||
// If some mod has an incompatability that is installed,
|
||||
// we need to ask for confirmation
|
||||
if (inc.mod) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// If some installed mod is incompatible with this one,
|
||||
// we need to ask for confirmation
|
||||
for (auto mod : Loader::get()->getAllMods()) {
|
||||
for (auto inc : mod->getMetadata().getIncompatibilities()) {
|
||||
if (inc.id == download.getID()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there are mods we aren't sure about yet, we can't auto-confirm
|
||||
else if (std::holds_alternative<DownloadStatusFetching>(status)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have reached this point, we can auto-confirm
|
||||
this->confirmAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<ModDownload> ModDownloadManager::getDownloads() const {
|
||||
return map::values(m_impl->m_downloads);
|
||||
}
|
||||
std::optional<ModDownload> ModDownloadManager::getDownload(std::string const& id) const {
|
||||
if (m_impl->m_downloads.contains(id)) {
|
||||
return m_impl->m_downloads.at(id);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
bool ModDownloadManager::hasActiveDownloads() const {
|
||||
for (auto& [_, download] : m_impl->m_downloads) {
|
||||
if (download.isActive()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModDownloadManager::wantsRestart() const {
|
||||
for (auto& [key, v] : m_impl->m_downloads) {
|
||||
if (v.isDone()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ModDownloadManager* ModDownloadManager::get() {
|
||||
static auto inst = new ModDownloadManager();
|
||||
return inst;
|
||||
}
|
||||
ModDownloadManager::ModDownloadManager() : m_impl(std::make_unique<Impl>()) {}
|
||||
ModDownloadManager::~ModDownloadManager() = default;
|
115
loader/src/server/DownloadManager.hpp
Normal file
115
loader/src/server/DownloadManager.hpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#pragma once
|
||||
|
||||
#include "Server.hpp"
|
||||
|
||||
namespace server {
|
||||
struct DownloadStatusFetching {
|
||||
uint8_t percentage;
|
||||
bool operator==(DownloadStatusFetching const&) const = default;
|
||||
};
|
||||
struct DownloadStatusConfirm {
|
||||
ServerModVersion version;
|
||||
bool operator==(DownloadStatusConfirm const&) const = default;
|
||||
};
|
||||
struct DownloadStatusDownloading {
|
||||
uint8_t percentage;
|
||||
bool operator==(DownloadStatusDownloading const&) const = default;
|
||||
};
|
||||
struct DownloadStatusDone {
|
||||
bool operator==(DownloadStatusDone const&) const = default;
|
||||
};
|
||||
struct DownloadStatusError {
|
||||
std::string details;
|
||||
bool operator==(DownloadStatusError const&) const = default;
|
||||
};
|
||||
struct DownloadStatusCancelled {
|
||||
bool operator==(DownloadStatusCancelled const&) const = default;
|
||||
};
|
||||
|
||||
using DownloadStatus = std::variant<
|
||||
DownloadStatusFetching,
|
||||
DownloadStatusConfirm,
|
||||
DownloadStatusDownloading,
|
||||
DownloadStatusDone,
|
||||
DownloadStatusError,
|
||||
DownloadStatusCancelled
|
||||
>;
|
||||
|
||||
struct ModDownloadEvent : public Event {
|
||||
std::string id;
|
||||
ModDownloadEvent(std::string const& id);
|
||||
};
|
||||
|
||||
class ModDownloadFilter : public EventFilter<ModDownloadEvent> {
|
||||
public:
|
||||
using Callback = void(ModDownloadEvent*);
|
||||
|
||||
protected:
|
||||
std::string m_id;
|
||||
|
||||
public:
|
||||
ListenerResult handle(MiniFunction<Callback> fn, ModDownloadEvent* event);
|
||||
|
||||
ModDownloadFilter();
|
||||
ModDownloadFilter(std::string const& id);
|
||||
};
|
||||
|
||||
class ModDownload final {
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
std::shared_ptr<Impl> m_impl;
|
||||
|
||||
ModDownload(
|
||||
std::string const& id,
|
||||
std::optional<VersionInfo> const& version,
|
||||
std::optional<std::string> const& dependencyFor
|
||||
);
|
||||
|
||||
friend class ModDownloadManager;
|
||||
|
||||
public:
|
||||
void confirm();
|
||||
void cancel();
|
||||
|
||||
bool isDone() const;
|
||||
bool isActive() const;
|
||||
bool canRetry() const;
|
||||
std::optional<std::string> getDependencyFor() const;
|
||||
std::string getID() const;
|
||||
DownloadStatus getStatus() const;
|
||||
std::optional<VersionInfo> getVersion() const;
|
||||
};
|
||||
|
||||
class ModDownloadManager final {
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
|
||||
ModDownloadManager();
|
||||
|
||||
friend class ModDownload;
|
||||
|
||||
public:
|
||||
static ModDownloadManager* get();
|
||||
~ModDownloadManager();
|
||||
|
||||
std::optional<ModDownload> startDownload(
|
||||
std::string const& id,
|
||||
std::optional<VersionInfo> const& version,
|
||||
std::optional<std::string> const& dependencyFor = std::nullopt
|
||||
);
|
||||
void startUpdateAll();
|
||||
void confirmAll();
|
||||
void cancelAll();
|
||||
void dismissAll();
|
||||
bool checkAutoConfirm();
|
||||
|
||||
std::optional<ModDownload> getDownload(std::string const& id) const;
|
||||
std::vector<ModDownload> getDownloads() const;
|
||||
bool hasActiveDownloads() const;
|
||||
|
||||
bool wantsRestart() const;
|
||||
};
|
||||
}
|
|
@ -76,55 +76,48 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
template <class Q, class V>
|
||||
using ServerFuncQ = V(*)(Q const&, bool);
|
||||
template <class V>
|
||||
using ServerFuncNQ = V(*)(bool);
|
||||
|
||||
template <class F>
|
||||
struct ExtractFun;
|
||||
|
||||
template <class Q, class V>
|
||||
struct ExtractFun<ServerRequest<V>(*)(Q const&, bool)> {
|
||||
using Query = Q;
|
||||
template <class V, class... Args>
|
||||
struct ExtractFun<ServerRequest<V>(*)(Args...)> {
|
||||
using CacheKey = std::tuple<std::remove_cvref_t<Args>...>;
|
||||
using Value = V;
|
||||
static ServerRequest<V> invoke(auto&& func, Query const& query) {
|
||||
return func(query, false);
|
||||
}
|
||||
};
|
||||
|
||||
template <class V>
|
||||
struct ExtractFun<ServerRequest<V>(*)(bool)> {
|
||||
using Query = std::monostate;
|
||||
using Value = V;
|
||||
static ServerRequest<V> invoke(auto&& func, Query const&) {
|
||||
return func(false);
|
||||
template <class... CArgs>
|
||||
static CacheKey key(CArgs const&... args) {
|
||||
return std::make_tuple(args..., false);
|
||||
}
|
||||
template <class... CArgs>
|
||||
static ServerRequest<V> invoke(auto&& func, CArgs const&... args) {
|
||||
return func(args..., false);
|
||||
}
|
||||
};
|
||||
|
||||
template <auto F>
|
||||
class FunCache final {
|
||||
public:
|
||||
using Extract = ExtractFun<decltype(F)>;
|
||||
using Query = typename Extract::Query;
|
||||
using Value = typename Extract::Value;
|
||||
using Extract = ExtractFun<decltype(F)>;
|
||||
using CacheKey = typename Extract::CacheKey;
|
||||
using Value = typename Extract::Value;
|
||||
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
CacheMap<Query, ServerRequest<Value>> m_cache;
|
||||
CacheMap<CacheKey, ServerRequest<Value>> m_cache;
|
||||
|
||||
public:
|
||||
FunCache() = default;
|
||||
FunCache(FunCache const&) = delete;
|
||||
FunCache(FunCache&&) = delete;
|
||||
|
||||
ServerRequest<Value> get(Query const& query = Query()) {
|
||||
template <class... Args>
|
||||
ServerRequest<Value> get(Args const&... args) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
if (auto v = m_cache.get(query)) {
|
||||
if (auto v = m_cache.get(Extract::key(args...))) {
|
||||
return *v;
|
||||
}
|
||||
auto f = Extract::invoke(F, query);
|
||||
m_cache.add(Query(query), ServerRequest<Value>(f));
|
||||
auto f = Extract::invoke(F, args...);
|
||||
m_cache.add(Extract::key(args...), ServerRequest<Value>(f));
|
||||
return f;
|
||||
}
|
||||
size_t size() {
|
||||
|
@ -305,10 +298,16 @@ Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) {
|
|||
}
|
||||
|
||||
ModMetadata::Dependency dependency;
|
||||
obj.needs("id").validate(MiniFunction<bool(std::string const&)>(&ModMetadata::validateID)).into(dependency.id);
|
||||
obj.needs("mod_id").validate(MiniFunction<bool(std::string const&)>(&ModMetadata::validateID)).into(dependency.id);
|
||||
obj.needs("version").into(dependency.version);
|
||||
obj.has("importance").into(dependency.importance);
|
||||
|
||||
// Check if this dependency is installed, and if so assign the `mod` member to mark that
|
||||
auto mod = Loader::get()->getInstalledMod(dependency.id);
|
||||
if (mod && dependency.version.compare(mod->getVersion())) {
|
||||
dependency.mod = mod;
|
||||
}
|
||||
|
||||
dependencies.push_back(dependency);
|
||||
}
|
||||
res.metadata.setDependencies(dependencies);
|
||||
|
@ -318,10 +317,16 @@ Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) {
|
|||
auto obj = incompat.obj();
|
||||
|
||||
ModMetadata::Incompatibility incompatibility;
|
||||
obj.needs("id").validate(MiniFunction<bool(std::string const&)>(&ModMetadata::validateID)).into(incompatibility.id);
|
||||
obj.needs("mod_id").validate(MiniFunction<bool(std::string const&)>(&ModMetadata::validateID)).into(incompatibility.id);
|
||||
obj.needs("version").into(incompatibility.version);
|
||||
obj.has("importance").into(incompatibility.importance);
|
||||
|
||||
// Check if this incompatability is installed, and if so assign the `mod` member to mark that
|
||||
auto mod = Loader::get()->getInstalledMod(incompatibility.id);
|
||||
if (mod && incompatibility.version.compare(mod->getVersion())) {
|
||||
incompatibility.mod = mod;
|
||||
}
|
||||
|
||||
incompatibilities.push_back(incompatibility);
|
||||
}
|
||||
res.metadata.setIncompatibilities(incompatibilities);
|
||||
|
@ -585,6 +590,38 @@ ServerRequest<ServerModMetadata> server::getMod(std::string const& id, bool useC
|
|||
);
|
||||
}
|
||||
|
||||
ServerRequest<ServerModVersion> server::getModVersion(std::string const& id, std::optional<VersionInfo> const& version, bool useCache) {
|
||||
if (useCache) {
|
||||
return getCache<getModVersion>().get(id, version);
|
||||
}
|
||||
|
||||
auto req = web::WebRequest();
|
||||
req.userAgent(getServerUserAgent());
|
||||
|
||||
auto versionURL = version ? version->toString(false) : "latest";
|
||||
return req.get(getServerAPIBaseURL() + "/mods/" + id + "/versions/" + versionURL).map(
|
||||
[](web::WebResponse* response) -> Result<ServerModVersion, ServerError> {
|
||||
if (response->ok()) {
|
||||
// Parse payload
|
||||
auto payload = parseServerPayload(*response);
|
||||
if (!payload) {
|
||||
return Err(payload.unwrapErr());
|
||||
}
|
||||
// Parse response
|
||||
auto list = ServerModVersion::parse(payload.unwrap());
|
||||
if (!list) {
|
||||
return Err(ServerError(response->code(), "Unable to parse response: {}", list.unwrapErr()));
|
||||
}
|
||||
return Ok(list.unwrap());
|
||||
}
|
||||
return Err(parseServerError(*response));
|
||||
},
|
||||
[id](web::WebProgress* progress) {
|
||||
return parseServerProgress(*progress, "Downloading metadata for " + id);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ServerRequest<ByteVector> server::getModLogo(std::string const& id, bool useCache) {
|
||||
if (useCache) {
|
||||
return getCache<getModLogo>().get(id);
|
||||
|
|
|
@ -29,6 +29,8 @@ namespace server {
|
|||
std::string hash;
|
||||
size_t downloadCount;
|
||||
|
||||
bool operator==(ServerModVersion const&) const = default;
|
||||
|
||||
static Result<ServerModVersion> parse(matjson::Value const& json);
|
||||
};
|
||||
|
||||
|
@ -116,12 +118,15 @@ namespace server {
|
|||
|
||||
std::string getServerAPIBaseURL();
|
||||
std::string getServerUserAgent();
|
||||
|
||||
ServerRequest<ServerModsList> getMods(ModsQuery const& query, bool useCache = true);
|
||||
ServerRequest<ServerModMetadata> getMod(std::string const& id, bool useCache = true);
|
||||
ServerRequest<ServerModVersion> getModVersion(std::string const& id, std::optional<VersionInfo> const& version = std::nullopt, bool useCache = true);
|
||||
ServerRequest<ByteVector> getModLogo(std::string const& id, bool useCache = true);
|
||||
ServerRequest<std::unordered_set<std::string>> getTags(bool useCache = true);
|
||||
|
||||
ServerRequest<std::optional<ServerModUpdate>> checkUpdates(Mod* mod);
|
||||
ServerRequest<std::vector<ServerModUpdate>> checkAllUpdates(bool useCache = true);
|
||||
|
||||
|
||||
void clearServerCaches(bool clearGlobalCaches = false);
|
||||
}
|
||||
|
|
|
@ -109,7 +109,9 @@ public:
|
|||
|
||||
void setVisible(bool visible) override {
|
||||
CCNode::setVisible(visible);
|
||||
this->spin();
|
||||
if (visible) {
|
||||
this->spin();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,8 +2,263 @@
|
|||
#include "SwelvyBG.hpp"
|
||||
#include <Geode/ui/TextInput.hpp>
|
||||
#include <Geode/utils/ColorProvider.hpp>
|
||||
#include "popups/ConfirmInstallPopup.hpp"
|
||||
#include "GeodeStyle.hpp"
|
||||
|
||||
bool ModsStatusNode::init() {
|
||||
if (!CCNode::init())
|
||||
return false;
|
||||
|
||||
this->ignoreAnchorPointForPosition(false);
|
||||
this->setAnchorPoint({ .5f, 1.f });
|
||||
this->setContentSize({ 300, 35 });
|
||||
|
||||
m_statusBG = CCScale9Sprite::create("black-square.png"_spr);
|
||||
m_statusBG->setContentSize({ 570, 40 });
|
||||
m_statusBG->setScale(.5f);
|
||||
|
||||
m_status = CCLabelBMFont::create("", "bigFont.fnt");
|
||||
m_status->setScale(.8f);
|
||||
m_statusBG->addChildAtPosition(m_status, Anchor::Center);
|
||||
|
||||
m_statusPercentage = CCLabelBMFont::create("", "bigFont.fnt");
|
||||
m_statusPercentage->setScale(.8f);
|
||||
m_statusBG->addChildAtPosition(m_statusPercentage, Anchor::Right, ccp(-25, 0));
|
||||
|
||||
m_loadingCircle = createLoadingCircle(32);
|
||||
m_statusBG->addChildAtPosition(m_loadingCircle, Anchor::Left, ccp(25, 0));
|
||||
|
||||
m_progressBar = Slider::create(nullptr, nullptr);
|
||||
m_progressBar->m_touchLogic->m_thumb->setVisible(false);
|
||||
m_progressBar->setScale(2.f);
|
||||
m_progressBar->setAnchorPoint({ 0, 0 }),
|
||||
m_statusBG->addChildAtPosition(m_progressBar, Anchor::Center);
|
||||
|
||||
this->addChildAtPosition(m_statusBG, Anchor::Bottom);
|
||||
|
||||
m_btnMenu = CCMenu::create();
|
||||
m_btnMenu->setContentWidth(m_obContentSize.width);
|
||||
|
||||
auto restartSpr = createGeodeButton("Restart Now");
|
||||
restartSpr->setScale(.65f);
|
||||
m_restartBtn = CCMenuItemSpriteExtra::create(
|
||||
restartSpr, this, menu_selector(ModsStatusNode::onRestart)
|
||||
);
|
||||
m_btnMenu->addChild(m_restartBtn);
|
||||
|
||||
auto viewSpr = createGeodeButton("View");
|
||||
viewSpr->setScale(.65f);
|
||||
m_viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, nullptr);
|
||||
m_btnMenu->addChild(m_viewBtn);
|
||||
|
||||
auto cancelSpr = createGeodeButton("Cancel");
|
||||
cancelSpr->setScale(.65f);
|
||||
m_cancelBtn = CCMenuItemSpriteExtra::create(
|
||||
cancelSpr, this, menu_selector(ModsStatusNode::onCancel)
|
||||
);
|
||||
m_btnMenu->addChild(m_cancelBtn);
|
||||
|
||||
m_btnMenu->setLayout(RowLayout::create());
|
||||
m_btnMenu->getLayout()->ignoreInvisibleChildren(true);
|
||||
this->addChildAtPosition(m_btnMenu, Anchor::Center, ccp(0, 5));
|
||||
|
||||
m_updateStateListener.bind([this](auto) { this->updateState(); });
|
||||
m_updateStateListener.setFilter(UpdateModListStateFilter());
|
||||
|
||||
m_downloadListener.bind([this](auto) { this->updateState(); });
|
||||
|
||||
this->updateState();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModsStatusNode::updateState() {
|
||||
enum class DownloadState {
|
||||
None,
|
||||
SomeCancelled,
|
||||
AllDone,
|
||||
SomeErrored,
|
||||
SomeToBeConfirmed,
|
||||
SomeFetching,
|
||||
SomeDownloading,
|
||||
};
|
||||
DownloadState state = DownloadState::None;
|
||||
auto upgradeState = [&](DownloadState into) {
|
||||
if (static_cast<int>(state) < static_cast<int>(into)) {
|
||||
state = into;
|
||||
}
|
||||
};
|
||||
|
||||
auto downloads = server::ModDownloadManager::get()->getDownloads();
|
||||
for (auto& download : downloads) {
|
||||
std::visit(makeVisitor {
|
||||
[&](server::DownloadStatusFetching const&) {
|
||||
upgradeState(DownloadState::SomeFetching);
|
||||
},
|
||||
[&](server::DownloadStatusConfirm const&) {
|
||||
upgradeState(DownloadState::SomeToBeConfirmed);
|
||||
},
|
||||
[&](server::DownloadStatusDownloading const&) {
|
||||
upgradeState(DownloadState::SomeDownloading);
|
||||
},
|
||||
[&](server::DownloadStatusDone const&) {
|
||||
upgradeState(DownloadState::AllDone);
|
||||
},
|
||||
[&](server::DownloadStatusError const&) {
|
||||
upgradeState(DownloadState::SomeErrored);
|
||||
},
|
||||
[&](server::DownloadStatusCancelled const&) {
|
||||
upgradeState(DownloadState::SomeCancelled);
|
||||
},
|
||||
}, download.getStatus());
|
||||
}
|
||||
|
||||
// Reset the state to default
|
||||
m_statusBG->setVisible(false);
|
||||
m_status->setVisible(false);
|
||||
m_statusPercentage->setVisible(false);
|
||||
m_loadingCircle->setVisible(false);
|
||||
m_progressBar->setVisible(false);
|
||||
m_restartBtn->setVisible(false);
|
||||
m_cancelBtn->setVisible(false);
|
||||
m_viewBtn->setVisible(false);
|
||||
|
||||
switch (state) {
|
||||
// If there are no downloads happening, just show the restart button if needed
|
||||
case DownloadState::None: {
|
||||
m_restartBtn->setVisible(isRestartRequired());
|
||||
} break;
|
||||
|
||||
// If some downloads were cancelled, show the restart button normally
|
||||
case DownloadState::SomeCancelled: {
|
||||
m_status->setString("Download(s) Cancelled");
|
||||
m_status->setColor(ccWHITE);
|
||||
m_status->setVisible(true);
|
||||
|
||||
m_restartBtn->setVisible(isRestartRequired());
|
||||
} break;
|
||||
|
||||
// If all downloads were finished, show the restart button normally
|
||||
// but also a "all done" status
|
||||
case DownloadState::AllDone: {
|
||||
m_status->setString(fmt::format("{} Mod(s) Installed/Updated", downloads.size()).c_str());
|
||||
m_status->setColor("mod-list-enabled"_cc3b);
|
||||
m_status->setVisible(true);
|
||||
m_statusBG->setVisible(true);
|
||||
|
||||
m_restartBtn->setVisible(isRestartRequired());
|
||||
} break;
|
||||
|
||||
case DownloadState::SomeErrored: {
|
||||
m_status->setString("Some Downloads Failed");
|
||||
m_status->setColor("mod-list-disabled"_cc3b);
|
||||
m_status->setVisible(true);
|
||||
m_statusBG->setVisible(true);
|
||||
|
||||
m_viewBtn->setVisible(true);
|
||||
m_viewBtn->setTarget(this, menu_selector(ModsStatusNode::onViewErrors));
|
||||
} break;
|
||||
|
||||
case DownloadState::SomeToBeConfirmed: {
|
||||
size_t totalToConfirm = 0;
|
||||
for (auto& download : downloads) {
|
||||
auto status = download.getStatus();
|
||||
if (auto loading = std::get_if<server::DownloadStatusConfirm>(&status)) {
|
||||
totalToConfirm += 1;
|
||||
}
|
||||
}
|
||||
m_status->setString(fmt::format("Click to Confirm {} Download(s)", totalToConfirm).c_str());
|
||||
m_status->setColor(ccWHITE);
|
||||
m_status->setVisible(true);
|
||||
m_statusBG->setVisible(true);
|
||||
|
||||
m_viewBtn->setVisible(true);
|
||||
m_viewBtn->setTarget(this, menu_selector(ModsStatusNode::onConfirm));
|
||||
} break;
|
||||
|
||||
case DownloadState::SomeFetching: {
|
||||
m_status->setString("Preparing Download(s)");
|
||||
m_status->setColor(ccWHITE);
|
||||
m_status->setVisible(true);
|
||||
m_loadingCircle->setVisible(true);
|
||||
m_statusBG->setVisible(true);
|
||||
|
||||
m_cancelBtn->setVisible(true);
|
||||
} break;
|
||||
|
||||
case DownloadState::SomeDownloading: {
|
||||
size_t totalProgress = 0;
|
||||
size_t totalDownloading = 0;
|
||||
for (auto& download : downloads) {
|
||||
auto status = download.getStatus();
|
||||
if (auto loading = std::get_if<server::DownloadStatusDownloading>(&status)) {
|
||||
totalProgress += loading->percentage;
|
||||
totalDownloading += 1;
|
||||
}
|
||||
}
|
||||
auto percentage = totalProgress / static_cast<float>(totalDownloading);
|
||||
|
||||
m_statusPercentage->setString(fmt::format("{}%", static_cast<size_t>(percentage)).c_str());
|
||||
m_statusPercentage->setVisible(true);
|
||||
m_loadingCircle->setVisible(true);
|
||||
m_statusBG->setVisible(true);
|
||||
|
||||
m_cancelBtn->setVisible(true);
|
||||
|
||||
m_progressBar->setVisible(true);
|
||||
m_progressBar->setValue(percentage / 100.f);
|
||||
m_progressBar->updateBar();
|
||||
} break;
|
||||
}
|
||||
|
||||
m_btnMenu->updateLayout();
|
||||
}
|
||||
|
||||
void ModsStatusNode::onViewErrors(CCObject*) {
|
||||
auto downloads = server::ModDownloadManager::get()->getDownloads();
|
||||
std::vector<std::string> errors;
|
||||
for (auto& download : downloads) {
|
||||
auto status = download.getStatus();
|
||||
if (auto error = std::get_if<server::DownloadStatusError>(&status)) {
|
||||
errors.push_back(fmt::format("<cr>{}</c>: {}", download.getID(), error->details));
|
||||
}
|
||||
}
|
||||
createQuickPopup(
|
||||
"Download Errors", ranges::join(errors, "\n"),
|
||||
"OK", "Dismiss",
|
||||
[](auto, bool btn2) {
|
||||
if (btn2) {
|
||||
server::ModDownloadManager::get()->dismissAll();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
void ModsStatusNode::onConfirm(CCObject*) {
|
||||
ConfirmInstallPopup::askForCustomize();
|
||||
}
|
||||
void ModsStatusNode::onCancel(CCObject*) {
|
||||
server::ModDownloadManager::get()->cancelAll();
|
||||
}
|
||||
void ModsStatusNode::onRestart(CCObject*) {
|
||||
// Update button state to let user know it's restarting but it might take a bit
|
||||
m_restartBtn->setEnabled(false);
|
||||
static_cast<ButtonSprite*>(m_restartBtn->getNormalImage())->setString("Restarting...");
|
||||
m_restartBtn->updateSprite();
|
||||
|
||||
// Actually restart
|
||||
game::restart();
|
||||
}
|
||||
|
||||
ModsStatusNode* ModsStatusNode::create() {
|
||||
auto ret = new ModsStatusNode();
|
||||
if (ret && ret->init()) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ModsLayer::init() {
|
||||
if (!CCLayer::init())
|
||||
return false;
|
||||
|
@ -124,19 +379,9 @@ bool ModsLayer::init() {
|
|||
listActionsMenu->setLayout(ColumnLayout::create());
|
||||
m_frame->addChildAtPosition(listActionsMenu, Anchor::Left, ccp(-5, 25));
|
||||
|
||||
auto restartMenu = CCMenu::create();
|
||||
restartMenu->setContentWidth(200.f);
|
||||
restartMenu->setAnchorPoint({ .5f, 1.f });
|
||||
restartMenu->setScale(.7f);
|
||||
|
||||
m_restartBtn = CCMenuItemSpriteExtra::create(
|
||||
createGeodeButton("Restart Now"),
|
||||
this, menu_selector(ModsLayer::onRestart)
|
||||
);
|
||||
restartMenu->addChild(m_restartBtn);
|
||||
|
||||
restartMenu->setLayout(RowLayout::create());
|
||||
m_frame->addChildAtPosition(restartMenu, Anchor::Bottom, ccp(0, 0));
|
||||
m_statusNode = ModsStatusNode::create();
|
||||
m_statusNode->setZOrder(4);
|
||||
m_frame->addChildAtPosition(m_statusNode, Anchor::Bottom);
|
||||
|
||||
m_pageMenu = CCMenu::create();
|
||||
m_pageMenu->setContentWidth(200.f);
|
||||
|
@ -191,13 +436,13 @@ void ModsLayer::gotoTab(ModListSource* src) {
|
|||
// Lazily create new list and add it to UI
|
||||
if (!m_lists.contains(src)) {
|
||||
auto list = ModList::create(src, m_frame->getContentSize() - ccp(30, 0));
|
||||
list->setPosition(m_frame->getPosition());
|
||||
this->addChild(list);
|
||||
list->setPosition(m_frame->getContentSize() / 2);
|
||||
m_frame->addChild(list);
|
||||
m_lists.emplace(src, list);
|
||||
}
|
||||
// Add list to UI
|
||||
else {
|
||||
this->addChild(m_lists.at(src));
|
||||
m_frame->addChild(m_lists.at(src));
|
||||
}
|
||||
|
||||
// Update current source
|
||||
|
@ -245,14 +490,6 @@ void ModsLayer::updateState() {
|
|||
else {
|
||||
m_pageMenu->setVisible(false);
|
||||
}
|
||||
|
||||
// Update visibility of the restart button
|
||||
m_restartBtn->setVisible(false);
|
||||
for (auto& [src, _] : m_lists) {
|
||||
if (src->wantsRestart()) {
|
||||
m_restartBtn->setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModsLayer::onTab(CCObject* sender) {
|
||||
|
@ -297,15 +534,6 @@ void ModsLayer::onSearch(CCObject*) {
|
|||
}
|
||||
}
|
||||
|
||||
void ModsLayer::onRestart(CCObject*) {
|
||||
// Update button state to let user know it's restarting but it might take a bit
|
||||
m_restartBtn->setEnabled(false);
|
||||
static_cast<ButtonSprite*>(m_restartBtn->getNormalImage())->setString("Restarting...");
|
||||
|
||||
// Actually restart
|
||||
game::restart();
|
||||
}
|
||||
|
||||
ModsLayer* ModsLayer::create() {
|
||||
auto ret = new ModsLayer();
|
||||
if (ret && ret->init()) {
|
||||
|
|
|
@ -7,9 +7,36 @@
|
|||
#include "list/ModList.hpp"
|
||||
#include "sources/ModListSource.hpp"
|
||||
#include "UpdateModListState.hpp"
|
||||
#include <server/DownloadManager.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
class ModsStatusNode : public CCNode {
|
||||
protected:
|
||||
CCScale9Sprite* m_statusBG;
|
||||
CCLabelBMFont* m_status;
|
||||
CCLabelBMFont* m_statusPercentage;
|
||||
Slider* m_progressBar;
|
||||
CCNode* m_loadingCircle;
|
||||
CCMenu* m_btnMenu;
|
||||
CCMenuItemSpriteExtra* m_viewBtn;
|
||||
CCMenuItemSpriteExtra* m_cancelBtn;
|
||||
CCMenuItemSpriteExtra* m_restartBtn;
|
||||
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||
EventListener<server::ModDownloadFilter> m_downloadListener;
|
||||
|
||||
bool init();
|
||||
void updateState();
|
||||
|
||||
void onRestart(CCObject*);
|
||||
void onViewErrors(CCObject*);
|
||||
void onConfirm(CCObject*);
|
||||
void onCancel(CCObject*);
|
||||
|
||||
public:
|
||||
static ModsStatusNode* create();
|
||||
};
|
||||
|
||||
class ModsLayer : public CCLayer, public SetTextPopupDelegate {
|
||||
protected:
|
||||
CCNode* m_frame;
|
||||
|
@ -19,7 +46,7 @@ protected:
|
|||
CCMenu* m_pageMenu;
|
||||
CCLabelBMFont* m_pageLabel;
|
||||
CCMenuItemSpriteExtra* m_goToPageBtn;
|
||||
CCMenuItemSpriteExtra* m_restartBtn;
|
||||
ModsStatusNode* m_statusNode;
|
||||
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||
bool m_showSearch = false;
|
||||
bool m_bigView = false;
|
||||
|
@ -33,9 +60,8 @@ protected:
|
|||
void onBigView(CCObject*);
|
||||
void onSearch(CCObject*);
|
||||
void onGoToPage(CCObject*);
|
||||
void onBack(CCObject*);
|
||||
void onRefreshList(CCObject*);
|
||||
void onRestart(CCObject*);
|
||||
void onBack(CCObject*);
|
||||
|
||||
void updateState();
|
||||
|
||||
|
|
|
@ -76,6 +76,34 @@ bool ModItem::init(ModSource&& source) {
|
|||
m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setMaxScale(.75f));
|
||||
m_infoContainer->addChild(m_restartRequiredLabel);
|
||||
|
||||
m_downloadBarContainer = CCNode::create();
|
||||
m_downloadBarContainer->setContentSize({ 225, 30 });
|
||||
|
||||
m_downloadBar = Slider::create(nullptr, nullptr);
|
||||
m_downloadBar->m_touchLogic->m_thumb->setVisible(false);
|
||||
m_downloadBar->setLayoutOptions(AxisLayoutOptions::create()->setMaxScale(.35f));
|
||||
m_downloadBarContainer->addChildAtPosition(m_downloadBar, Anchor::Center, ccp(0, 0), ccp(0, 0));
|
||||
|
||||
m_infoContainer->addChild(m_downloadBarContainer);
|
||||
|
||||
m_downloadWaiting = CCNode::create();
|
||||
m_downloadWaiting->setContentSize({ 225, 30 });
|
||||
|
||||
auto downloadWaitingLabel = CCLabelBMFont::create("Preparing Download...", "bigFont.fnt");
|
||||
downloadWaitingLabel->setScale(.75f);
|
||||
m_downloadWaiting->addChildAtPosition(
|
||||
downloadWaitingLabel, Anchor::Left,
|
||||
ccp(m_downloadWaiting->getContentHeight(), 0), ccp(0, .5f)
|
||||
);
|
||||
|
||||
auto downloadWaitingSpinner = createLoadingCircle(20);
|
||||
m_downloadWaiting->addChildAtPosition(
|
||||
downloadWaitingSpinner, Anchor::Left,
|
||||
ccp(m_downloadWaiting->getContentHeight() / 2, 0)
|
||||
);
|
||||
|
||||
m_infoContainer->addChild(m_downloadWaiting);
|
||||
|
||||
this->addChild(m_infoContainer);
|
||||
|
||||
m_viewMenu = CCMenu::create();
|
||||
|
@ -142,16 +170,47 @@ bool ModItem::init(ModSource&& source) {
|
|||
this->updateState();
|
||||
|
||||
// Only listen for updates on this mod specifically
|
||||
m_updateStateListener.setFilter(UpdateModListStateFilter(UpdateModState(m_source.getID())));
|
||||
m_updateStateListener.bind([this](auto) { this->updateState(); });
|
||||
m_updateStateListener.setFilter(UpdateModListStateFilter(UpdateModState(m_source.getID())));
|
||||
|
||||
m_downloadListener.bind([this](auto) { this->updateState(); });
|
||||
m_downloadListener.setFilter(server::ModDownloadFilter(m_source.getID()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModItem::updateState() {
|
||||
auto wantsRestart = m_source.wantsRestart();
|
||||
m_restartRequiredLabel->setVisible(wantsRestart);
|
||||
m_developers->setVisible(!wantsRestart);
|
||||
|
||||
auto download = server::ModDownloadManager::get()->getDownload(m_source.getID());
|
||||
|
||||
// If there is an active download ongoing, show that in place of developer name
|
||||
if (download && download->isActive()) {
|
||||
m_updateBtn->setVisible(false);
|
||||
m_restartRequiredLabel->setVisible(false);
|
||||
m_developers->setVisible(false);
|
||||
|
||||
auto status = download->getStatus();
|
||||
if (auto prog = std::get_if<server::DownloadStatusDownloading>(&status)) {
|
||||
m_downloadWaiting->setVisible(false);
|
||||
m_downloadBarContainer->setVisible(true);
|
||||
m_downloadBar->setValue(prog->percentage / 100.f);
|
||||
m_downloadBar->updateBar();
|
||||
}
|
||||
else {
|
||||
m_downloadBarContainer->setVisible(false);
|
||||
m_downloadWaiting->setVisible(true);
|
||||
// Make sure the spinner is spinning by ticking its setVisible
|
||||
m_downloadWaiting->getChildByID("loading-spinner")->setVisible(true);
|
||||
}
|
||||
}
|
||||
// Otherwise show "Restart Required" button if needed in place of dev name
|
||||
else {
|
||||
m_restartRequiredLabel->setVisible(wantsRestart);
|
||||
m_developers->setVisible(!wantsRestart);
|
||||
m_downloadBarContainer->setVisible(false);
|
||||
m_downloadWaiting->setVisible(false);
|
||||
}
|
||||
m_infoContainer->updateLayout();
|
||||
|
||||
// Set default colors based on source to start off with
|
||||
|
@ -174,7 +233,10 @@ void ModItem::updateState() {
|
|||
},
|
||||
});
|
||||
|
||||
if (auto update = m_source.hasUpdates()) {
|
||||
if (
|
||||
auto update = m_source.hasUpdates();
|
||||
update && !(download && (download->isActive() || download->isDone()))
|
||||
) {
|
||||
m_updateBtn->setVisible(true);
|
||||
auto updateString = m_source.getMetadata().getVersion().toString() + " -> " + update->version.toString();
|
||||
m_versionLabel->setString(updateString.c_str());
|
||||
|
@ -276,12 +338,7 @@ void ModItem::onEnable(CCObject*) {
|
|||
}
|
||||
|
||||
void ModItem::onInstall(CCObject*) {
|
||||
if (auto data = m_source.asServer()) {
|
||||
|
||||
}
|
||||
|
||||
// Update state of the mod item
|
||||
UpdateModListStateEvent(UpdateModState(m_source.getID())).post();
|
||||
server::ModDownloadManager::get()->startDownload(m_source.getID(), std::nullopt);
|
||||
}
|
||||
|
||||
ModItem* ModItem::create(ModSource&& source) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <Geode/ui/General.hpp>
|
||||
#include <server/Server.hpp>
|
||||
#include <server/DownloadManager.hpp>
|
||||
#include "../sources/ModSource.hpp"
|
||||
#include "../UpdateModListState.hpp"
|
||||
|
||||
|
@ -19,11 +20,15 @@ protected:
|
|||
CCNode* m_developers;
|
||||
CCLabelBMFont* m_developerLabel;
|
||||
ButtonSprite* m_restartRequiredLabel;
|
||||
CCNode* m_downloadWaiting;
|
||||
CCNode* m_downloadBarContainer;
|
||||
Slider* m_downloadBar;
|
||||
CCMenu* m_viewMenu;
|
||||
CCMenuItemToggler* m_enableToggle = nullptr;
|
||||
CCMenuItemSpriteExtra* m_updateBtn = nullptr;
|
||||
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||
EventListener<server::ServerRequest<std::optional<server::ServerModUpdate>>> m_checkUpdateListener;
|
||||
EventListener<server::ModDownloadFilter> m_downloadListener;
|
||||
std::optional<server::ServerModUpdate> m_availableUpdate;
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,6 +30,79 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
|
|||
m_topContainer->setContentWidth(size.width);
|
||||
m_topContainer->setAnchorPoint({ .5f, 1.f });
|
||||
|
||||
// Check for updates on installed mods, and show an update all button if there are some
|
||||
if (typeinfo_cast<InstalledModListSource*>(m_source)) {
|
||||
m_checkUpdatesListener.bind(this, &ModList::onCheckUpdates);
|
||||
m_checkUpdatesListener.setFilter(ModsLayer::checkInstalledModsForUpdates());
|
||||
|
||||
m_updateAllContainer = CCNode::create();
|
||||
m_updateAllContainer->ignoreAnchorPointForPosition(false);
|
||||
m_updateAllContainer->setContentSize({ size.width, 30 });
|
||||
m_updateAllContainer->setVisible(false);
|
||||
|
||||
auto updateAllBG = CCLayerGradient::create(
|
||||
"mod-list-updates-available-bg"_cc4b,
|
||||
"mod-list-updates-available-bg-2"_cc4b,
|
||||
ccp(1, -.5f)
|
||||
);
|
||||
updateAllBG->setContentSize(m_updateAllContainer->getContentSize());
|
||||
updateAllBG->ignoreAnchorPointForPosition(false);
|
||||
|
||||
updateAllBG->addChildAtPosition(
|
||||
CCLayerColor::create("mod-list-bg"_cc4b, m_updateAllContainer->getContentWidth(), 1),
|
||||
Anchor::TopLeft
|
||||
);
|
||||
updateAllBG->addChildAtPosition(
|
||||
CCLayerColor::create("mod-list-bg"_cc4b, m_updateAllContainer->getContentWidth(), 1),
|
||||
Anchor::BottomLeft, ccp(0, -1)
|
||||
);
|
||||
|
||||
m_updateAllContainer->addChildAtPosition(updateAllBG, Anchor::Center);
|
||||
|
||||
m_updateCountLabel = TextArea::create("", "bigFont.fnt", .35f, size.width / 2 - 30, ccp(0, 1), 12.f, false);
|
||||
m_updateAllContainer->addChildAtPosition(m_updateCountLabel, Anchor::Left, ccp(10, 0), ccp(0, 0));
|
||||
|
||||
m_updateAllMenu = CCMenu::create();
|
||||
m_updateAllMenu->setContentWidth(size.width / 2);
|
||||
m_updateAllMenu->setAnchorPoint({ 1, .5f });
|
||||
|
||||
auto showUpdatesSpr = createGeodeButton(
|
||||
CCSprite::createWithSpriteFrameName("GJ_filterIcon_001.png"),
|
||||
"Show Updates", "GE_button_01.png"_spr
|
||||
);
|
||||
auto hideUpdatesSpr = createGeodeButton(
|
||||
CCSprite::createWithSpriteFrameName("GJ_filterIcon_001.png"),
|
||||
"Hide Updates", "GE_button_05.png"_spr
|
||||
);
|
||||
m_toggleUpdatesOnlyBtn = CCMenuItemToggler::create(
|
||||
showUpdatesSpr, hideUpdatesSpr, this, menu_selector(ModList::onToggleUpdates)
|
||||
);
|
||||
m_toggleUpdatesOnlyBtn->m_notClickable = true;
|
||||
m_updateAllMenu->addChild(m_toggleUpdatesOnlyBtn);
|
||||
|
||||
auto updateAllSpr = createGeodeButton(
|
||||
CCSprite::createWithSpriteFrameName("update.png"_spr),
|
||||
"Update All", "GE_button_01.png"_spr
|
||||
);
|
||||
m_updateAllBtn = CCMenuItemSpriteExtra::create(
|
||||
updateAllSpr, this, menu_selector(ModList::onUpdateAll)
|
||||
);
|
||||
m_updateAllMenu->addChild(m_updateAllBtn);
|
||||
|
||||
m_updateAllLoadingCircle = createLoadingCircle(32);
|
||||
m_updateAllMenu->addChild(m_updateAllLoadingCircle);
|
||||
|
||||
m_updateAllMenu->setLayout(
|
||||
RowLayout::create()
|
||||
->setAxisAlignment(AxisAlignment::End)
|
||||
->setDefaultScaleLimits(.1f, .75f)
|
||||
);
|
||||
m_updateAllMenu->getLayout()->ignoreInvisibleChildren(true);
|
||||
m_updateAllContainer->addChildAtPosition(m_updateAllMenu, Anchor::Right, ccp(-10, 0));
|
||||
|
||||
m_topContainer->addChild(m_updateAllContainer);
|
||||
}
|
||||
|
||||
m_searchMenu = CCNode::create();
|
||||
m_searchMenu->ignoreAnchorPointForPosition(false);
|
||||
m_searchMenu->setContentSize({ size.width, 30 });
|
||||
|
@ -104,65 +177,6 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
|
|||
|
||||
m_topContainer->addChild(m_searchMenu);
|
||||
|
||||
// Check for updates on installed mods, and show an update all button if there are some
|
||||
if (typeinfo_cast<InstalledModListSource*>(m_source)) {
|
||||
m_checkUpdatesListener.bind(this, &ModList::onCheckUpdates);
|
||||
m_checkUpdatesListener.setFilter(ModsLayer::checkInstalledModsForUpdates());
|
||||
|
||||
m_updateAllMenu = CCNode::create();
|
||||
m_updateAllMenu->ignoreAnchorPointForPosition(false);
|
||||
m_updateAllMenu->setContentSize({ size.width, 30 });
|
||||
m_updateAllMenu->setVisible(false);
|
||||
|
||||
auto updateAllBG = CCLayerGradient::create(
|
||||
ColorProvider::get()->color("mod-list-updates-available-bg"_spr),
|
||||
ColorProvider::get()->color("mod-list-updates-available-bg-2"_spr),
|
||||
ccp(1, -.5f)
|
||||
);
|
||||
updateAllBG->setContentSize(m_updateAllMenu->getContentSize());
|
||||
updateAllBG->ignoreAnchorPointForPosition(false);
|
||||
m_updateAllMenu->addChildAtPosition(updateAllBG, Anchor::Center);
|
||||
|
||||
m_updateCountLabel = TextArea::create("", "bigFont.fnt", .35f, size.width / 2 - 30, ccp(0, 1), 12.f, false);
|
||||
m_updateAllMenu->addChildAtPosition(m_updateCountLabel, Anchor::Left, ccp(10, 0), ccp(0, 0));
|
||||
|
||||
auto updateAllMenu = CCMenu::create();
|
||||
updateAllMenu->setContentWidth(size.width / 2);
|
||||
updateAllMenu->setAnchorPoint({ 1, .5f });
|
||||
|
||||
auto showUpdatesSpr = createGeodeButton(
|
||||
CCSprite::createWithSpriteFrameName("GJ_filterIcon_001.png"),
|
||||
"Show Updates", "GE_button_01.png"_spr
|
||||
);
|
||||
auto hideUpdatesSpr = createGeodeButton(
|
||||
CCSprite::createWithSpriteFrameName("GJ_filterIcon_001.png"),
|
||||
"Hide Updates", "GE_button_05.png"_spr
|
||||
);
|
||||
m_toggleUpdatesOnlyBtn = CCMenuItemToggler::create(
|
||||
showUpdatesSpr, hideUpdatesSpr, this, menu_selector(ModList::onToggleUpdates)
|
||||
);
|
||||
m_toggleUpdatesOnlyBtn->m_notClickable = true;
|
||||
updateAllMenu->addChild(m_toggleUpdatesOnlyBtn);
|
||||
|
||||
auto updateAllSpr = createGeodeButton(
|
||||
CCSprite::createWithSpriteFrameName("update.png"_spr),
|
||||
"Update All", "GE_button_01.png"_spr
|
||||
);
|
||||
auto updateAllBtn = CCMenuItemSpriteExtra::create(
|
||||
updateAllSpr, this, menu_selector(ModList::onUpdateAll)
|
||||
);
|
||||
updateAllMenu->addChild(updateAllBtn);
|
||||
|
||||
updateAllMenu->setLayout(
|
||||
RowLayout::create()
|
||||
->setAxisAlignment(AxisAlignment::End)
|
||||
->setDefaultScaleLimits(.1f, 1.f)
|
||||
);
|
||||
m_updateAllMenu->addChildAtPosition(updateAllMenu, Anchor::Right, ccp(-10, 0));
|
||||
|
||||
m_topContainer->addChild(m_updateAllMenu);
|
||||
}
|
||||
|
||||
m_topContainer->setLayout(
|
||||
ColumnLayout::create()
|
||||
->setGap(0)
|
||||
|
@ -255,6 +269,8 @@ bool ModList::init(ModListSource* src, CCSize const& size) {
|
|||
|
||||
m_invalidateCacheListener.bind(this, &ModList::onInvalidateCache);
|
||||
m_invalidateCacheListener.setFilter(InvalidateCacheFilter(m_source));
|
||||
|
||||
m_downloadListener.bind([this](auto) { this->updateTopContainer(); });
|
||||
|
||||
this->gotoPage(0);
|
||||
this->updateTopContainer();
|
||||
|
@ -355,7 +371,7 @@ void ModList::onCheckUpdates(typename server::ServerRequest<std::vector<std::str
|
|||
fmt = fmt::format("There are <cg>{}</c> updates available!", mods.size());
|
||||
}
|
||||
m_updateCountLabel->setString(fmt.c_str());
|
||||
m_updateAllMenu->setVisible(true);
|
||||
m_updateAllContainer->setVisible(true);
|
||||
this->updateTopContainer();
|
||||
}
|
||||
}
|
||||
|
@ -393,6 +409,15 @@ void ModList::updateTopContainer() {
|
|||
m_list->m_contentLayer->getContentHeight() - m_list->getContentHeight()
|
||||
) * oldPosition);
|
||||
|
||||
// If there are active downloads, hide the Update All button
|
||||
if (m_updateAllContainer) {
|
||||
auto shouldShowLoading = server::ModDownloadManager::get()->hasActiveDownloads();
|
||||
m_updateAllBtn->setEnabled(!shouldShowLoading);
|
||||
static_cast<IconButtonSprite*>(m_updateAllBtn->getNormalImage())->setOpacity(shouldShowLoading ? 90 : 255);
|
||||
m_updateAllLoadingCircle->setVisible(shouldShowLoading);
|
||||
m_updateAllMenu->updateLayout();
|
||||
}
|
||||
|
||||
// ModList uses an anchor layout, so this puts the list in the right place
|
||||
this->updateLayout();
|
||||
}
|
||||
|
@ -504,7 +529,9 @@ void ModList::onToggleUpdates(CCObject*) {
|
|||
}
|
||||
}
|
||||
|
||||
void ModList::onUpdateAll(CCObject*) {}
|
||||
void ModList::onUpdateAll(CCObject*) {
|
||||
server::ModDownloadManager::get()->startUpdateAll();
|
||||
}
|
||||
|
||||
size_t ModList::getPage() const {
|
||||
return m_page;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <Geode/ui/TextInput.hpp>
|
||||
#include "ModItem.hpp"
|
||||
#include "../sources/ModListSource.hpp"
|
||||
#include <server/DownloadManager.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
|
@ -32,12 +33,16 @@ protected:
|
|||
CCMenuItemSpriteExtra* m_pageNextBtn;
|
||||
CCNode* m_topContainer;
|
||||
CCNode* m_searchMenu;
|
||||
CCNode* m_updateAllMenu = nullptr;
|
||||
CCNode* m_updateAllContainer = nullptr;
|
||||
CCMenu* m_updateAllMenu = nullptr;
|
||||
CCMenuItemSpriteExtra* m_updateAllBtn = nullptr;
|
||||
CCNode* m_updateAllLoadingCircle = nullptr;
|
||||
CCMenuItemToggler* m_toggleUpdatesOnlyBtn = nullptr;
|
||||
TextArea* m_updateCountLabel = nullptr;
|
||||
TextInput* m_searchInput;
|
||||
EventListener<InvalidateCacheFilter> m_invalidateCacheListener;
|
||||
EventListener<server::ServerRequest<std::vector<std::string>>> m_checkUpdatesListener;
|
||||
EventListener<server::ModDownloadFilter> m_downloadListener;
|
||||
bool m_bigSize = false;
|
||||
std::atomic<size_t> m_searchInputThreads = 0;
|
||||
|
||||
|
|
162
loader/src/ui/mods/popups/ConfirmInstallPopup.cpp
Normal file
162
loader/src/ui/mods/popups/ConfirmInstallPopup.cpp
Normal file
|
@ -0,0 +1,162 @@
|
|||
#include "ConfirmInstallPopup.hpp"
|
||||
#include <Geode/ui/GeodeUI.hpp>
|
||||
|
||||
class SmallModItem : public CCNode {
|
||||
protected:
|
||||
bool init(ModMetadata const& metadata) {
|
||||
if (!CCNode::init())
|
||||
return false;
|
||||
|
||||
this->setContentSize({ 60, 80 });
|
||||
|
||||
auto logo = createServerModLogo(metadata.getID());
|
||||
limitNodeSize(logo, { 45, 45 }, 5.f, .1f);
|
||||
this->addChildAtPosition(logo, Anchor::Center, ccp(0, 10));
|
||||
|
||||
auto title = CCLabelBMFont::create(metadata.getName().c_str(), "bigFont.fnt");
|
||||
title->limitLabelWidth(m_obContentSize.width, .3f, .1f);
|
||||
this->addChildAtPosition(title, Anchor::Center, ccp(0, -20));
|
||||
|
||||
auto developer = CCLabelBMFont::create(fmt::format("by {}", metadata.getDeveloper()).c_str(), "goldFont.fnt");
|
||||
developer->limitLabelWidth(m_obContentSize.width, .3f, .1f);
|
||||
this->addChildAtPosition(developer, Anchor::Center, ccp(0, -30));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
static SmallModItem* create(ModMetadata const& metadata) {
|
||||
auto ret = new SmallModItem();
|
||||
if (ret && ret->init(metadata)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
bool ConfirmInstallPopup::setup() {
|
||||
m_noElasticity = true;
|
||||
|
||||
this->setTitle("Customize Installation");
|
||||
|
||||
auto installationsTitle = CCLabelBMFont::create("You are Installing:", "bigFont.fnt");
|
||||
installationsTitle->setScale(.5f);
|
||||
m_mainLayer->addChildAtPosition(installationsTitle, Anchor::Center, ccp(0, 100));
|
||||
|
||||
m_installations = CCNode::create();
|
||||
m_installations->setAnchorPoint({ .5f, .5f });
|
||||
m_installations->setContentSize({ m_size.width, 60 });
|
||||
m_installations->setLayout(
|
||||
RowLayout::create()
|
||||
->setDefaultScaleLimits(.1f, .65f)
|
||||
->setGrowCrossAxis(true)
|
||||
->setCrossAxisOverflow(false)
|
||||
);
|
||||
m_mainLayer->addChildAtPosition(m_installations, Anchor::Center, ccp(0, 60));
|
||||
|
||||
auto dependenciesTitle = CCLabelBMFont::create("Which Depend(s) on:", "bigFont.fnt");
|
||||
dependenciesTitle->setScale(.5f);
|
||||
m_mainLayer->addChildAtPosition(dependenciesTitle, Anchor::Center, ccp(0, 20));
|
||||
|
||||
m_dependencies = CCNode::create();
|
||||
m_dependencies->setAnchorPoint({ .5f, .5f });
|
||||
m_dependencies->setContentSize({ m_size.width, 60 });
|
||||
m_dependencies->setLayout(
|
||||
RowLayout::create()
|
||||
->setDefaultScaleLimits(.1f, .65f)
|
||||
->setGrowCrossAxis(true)
|
||||
->setCrossAxisOverflow(false)
|
||||
);
|
||||
m_mainLayer->addChildAtPosition(m_dependencies, Anchor::Center, ccp(0, -20));
|
||||
|
||||
auto incompatablitiesTitle = CCLabelBMFont::create("But is/are Incompatible with:", "bigFont.fnt");
|
||||
incompatablitiesTitle->setScale(.5f);
|
||||
m_mainLayer->addChildAtPosition(incompatablitiesTitle, Anchor::Center, ccp(0, -60));
|
||||
|
||||
m_incompatabilities = CCNode::create();
|
||||
m_incompatabilities->setAnchorPoint({ .5f, .5f });
|
||||
m_incompatabilities->setContentSize({ m_size.width, 60 });
|
||||
m_incompatabilities->setLayout(
|
||||
RowLayout::create()
|
||||
->setDefaultScaleLimits(.1f, .65f)
|
||||
->setGrowCrossAxis(true)
|
||||
->setCrossAxisOverflow(false)
|
||||
);
|
||||
m_mainLayer->addChildAtPosition(m_incompatabilities, Anchor::Center, ccp(0, -100));
|
||||
|
||||
m_downloadListener.bind([this](auto) { this->updateState(); });
|
||||
|
||||
this->updateState();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfirmInstallPopup::updateState() {
|
||||
m_installations->removeAllChildren();
|
||||
m_dependencies->removeAllChildren();
|
||||
m_incompatabilities->removeAllChildren();
|
||||
|
||||
for (auto& download : server::ModDownloadManager::get()->getDownloads()) {
|
||||
auto status = download.getStatus();
|
||||
if (auto confirm = std::get_if<server::DownloadStatusConfirm>(&status)) {
|
||||
if (download.getDependencyFor()) {
|
||||
m_dependencies->addChild(SmallModItem::create(confirm->version.metadata));
|
||||
}
|
||||
else {
|
||||
m_installations->addChild(SmallModItem::create(confirm->version.metadata));
|
||||
}
|
||||
for (auto& inc : confirm->version.metadata.getIncompatibilities()) {
|
||||
if (inc.mod) {
|
||||
m_installations->addChild(SmallModItem::create(inc.mod->getMetadata()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_installations->updateLayout();
|
||||
m_dependencies->updateLayout();
|
||||
m_incompatabilities->updateLayout();
|
||||
}
|
||||
|
||||
ConfirmInstallPopup* ConfirmInstallPopup::create() {
|
||||
auto ret = new ConfirmInstallPopup();
|
||||
if (ret && ret->init(350, 280)) {
|
||||
ret->autorelease();
|
||||
return ret;
|
||||
}
|
||||
CC_SAFE_DELETE(ret);
|
||||
return nullptr;
|
||||
}
|
||||
void ConfirmInstallPopup::askForCustomize() {
|
||||
size_t confirmCount = 0;
|
||||
size_t dependencyCount = 0;
|
||||
for (auto& download : server::ModDownloadManager::get()->getDownloads()) {
|
||||
auto status = download.getStatus();
|
||||
if (auto confirm = std::get_if<server::DownloadStatusConfirm>(&status)) {
|
||||
confirmCount += 1;
|
||||
if (download.getDependencyFor()) {
|
||||
dependencyCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createQuickPopup(
|
||||
"Confirm Install",
|
||||
fmt::format(
|
||||
"<cy>{} mods</c> will be installed, out of which <cr>{}</c> are <cr>dependencies</c>. "
|
||||
"Do you want to <cb>customize</c> the installation, or continue with default options?",
|
||||
confirmCount, dependencyCount
|
||||
),
|
||||
"Customize", "Continue",
|
||||
[](auto*, bool btn2) {
|
||||
if (btn2) {
|
||||
server::ModDownloadManager::get()->confirmAll();
|
||||
}
|
||||
else {
|
||||
ConfirmInstallPopup::create()->show();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
23
loader/src/ui/mods/popups/ConfirmInstallPopup.hpp
Normal file
23
loader/src/ui/mods/popups/ConfirmInstallPopup.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <Geode/ui/Popup.hpp>
|
||||
#include <server/DownloadManager.hpp>
|
||||
#include "../GeodeStyle.hpp"
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
class ConfirmInstallPopup : public GeodePopup<> {
|
||||
protected:
|
||||
CCNode* m_installations;
|
||||
CCNode* m_dependencies;
|
||||
CCNode* m_incompatabilities;
|
||||
EventListener<server::ModDownloadFilter> m_downloadListener;
|
||||
|
||||
bool setup() override;
|
||||
|
||||
void updateState();
|
||||
|
||||
public:
|
||||
static ConfirmInstallPopup* create();
|
||||
static void askForCustomize();
|
||||
};
|
|
@ -267,7 +267,7 @@ bool ModPopup::setup(ModSource&& src) {
|
|||
);
|
||||
updateModSpr->setScale(.5f);
|
||||
m_updateBtn = CCMenuItemSpriteExtra::create(
|
||||
updateModSpr, this, nullptr
|
||||
updateModSpr, this, menu_selector(ModPopup::onInstall)
|
||||
);
|
||||
m_installMenu->addChild(m_updateBtn);
|
||||
|
||||
|
@ -316,7 +316,7 @@ bool ModPopup::setup(ModSource&& src) {
|
|||
);
|
||||
installModSpr->setScale(.5f);
|
||||
m_installBtn = CCMenuItemSpriteExtra::create(
|
||||
installModSpr, this, nullptr
|
||||
installModSpr, this, menu_selector(ModPopup::onInstall)
|
||||
);
|
||||
m_installMenu->addChild(m_installBtn);
|
||||
|
||||
|
@ -331,6 +331,17 @@ bool ModPopup::setup(ModSource&& src) {
|
|||
);
|
||||
m_installMenu->addChild(m_uninstallBtn);
|
||||
|
||||
auto cancelDownloadSpr = createGeodeButton(
|
||||
CCSprite::createWithSpriteFrameName("GJ_deleteIcon_001.png"),
|
||||
"Cancel",
|
||||
"GE_button_05.png"_spr
|
||||
);
|
||||
cancelDownloadSpr->setScale(.5f);
|
||||
m_cancelBtn = CCMenuItemSpriteExtra::create(
|
||||
cancelDownloadSpr, this, menu_selector(ModPopup::onCancelDownload)
|
||||
);
|
||||
m_installMenu->addChild(m_cancelBtn);
|
||||
|
||||
m_installStatusLabel = CCLabelBMFont::create("", "bigFont.fnt");
|
||||
m_installStatusLabel->setOpacity(120);
|
||||
m_installStatusLabel->setVisible(false);
|
||||
|
@ -489,8 +500,11 @@ bool ModPopup::setup(ModSource&& src) {
|
|||
}
|
||||
|
||||
// Only listen for updates on this mod specifically
|
||||
m_updateStateListener.setFilter(UpdateModListStateFilter(UpdateModState(m_source.getID())));
|
||||
m_updateStateListener.bind([this](auto) { this->updateState(); });
|
||||
m_updateStateListener.setFilter(UpdateModListStateFilter(UpdateModState(m_source.getID())));
|
||||
|
||||
m_downloadListener.bind([this](auto) { this->updateState(); });
|
||||
m_downloadListener.setFilter(m_source.getID());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -500,16 +514,6 @@ void ModPopup::updateState() {
|
|||
auto asServer = m_source.asServer();
|
||||
auto wantsRestart = m_source.wantsRestart();
|
||||
|
||||
m_enableBtn->toggle(asMod && asMod->isOrWillBeEnabled());
|
||||
m_enableBtn->setVisible(asMod && asMod->getRequestedAction() == ModRequestedAction::None);
|
||||
|
||||
m_reenableBtn->toggle(m_enableBtn->isToggled());
|
||||
m_reenableBtn->setVisible(asMod && modRequestedActionIsToggle(asMod->getRequestedAction()));
|
||||
|
||||
m_updateBtn->setVisible(m_source.hasUpdates().has_value() && asMod->getRequestedAction() == ModRequestedAction::None);
|
||||
m_installBtn->setVisible(asServer);
|
||||
m_uninstallBtn->setVisible(asMod && asMod->getRequestedAction() == ModRequestedAction::None);
|
||||
|
||||
m_installBG->setColor(wantsRestart ? to3B(ColorProvider::get()->color("mod-list-restart-required-label"_spr)) : ccc3(0, 0, 0));
|
||||
m_installBG->setOpacity(wantsRestart ? 40 : 75);
|
||||
m_restartRequiredLabel->setVisible(wantsRestart);
|
||||
|
@ -529,6 +533,18 @@ void ModPopup::updateState() {
|
|||
m_enabledStatusLabel->setVisible(false);
|
||||
}
|
||||
|
||||
m_cancelBtn->setVisible(false);
|
||||
|
||||
m_enableBtn->toggle(asMod && asMod->isOrWillBeEnabled());
|
||||
m_enableBtn->setVisible(asMod && asMod->getRequestedAction() == ModRequestedAction::None);
|
||||
|
||||
m_reenableBtn->toggle(m_enableBtn->isToggled());
|
||||
m_reenableBtn->setVisible(asMod && modRequestedActionIsToggle(asMod->getRequestedAction()));
|
||||
|
||||
m_updateBtn->setVisible(m_source.hasUpdates().has_value() && asMod->getRequestedAction() == ModRequestedAction::None);
|
||||
m_installBtn->setVisible(asServer);
|
||||
m_uninstallBtn->setVisible(asMod && asMod->getRequestedAction() == ModRequestedAction::None);
|
||||
|
||||
if (asMod && modRequestedActionIsUninstall(asMod->getRequestedAction())) {
|
||||
m_installStatusLabel->setString("Mod has been uninstalled");
|
||||
m_installStatusLabel->setVisible(true);
|
||||
|
@ -558,6 +574,60 @@ void ModPopup::updateState() {
|
|||
m_installStatusLabel->setVisible(true);
|
||||
}
|
||||
|
||||
auto download = server::ModDownloadManager::get()->getDownload(m_source.getID());
|
||||
if (download) {
|
||||
if (download->isActive()) {
|
||||
m_enableBtn->setVisible(false);
|
||||
m_reenableBtn->setVisible(false);
|
||||
m_updateBtn->setVisible(false);
|
||||
m_installBtn->setVisible(false);
|
||||
m_uninstallBtn->setVisible(false);
|
||||
m_cancelBtn->setVisible(true);
|
||||
|
||||
auto status = download->getStatus();
|
||||
if (auto d = std::get_if<server::DownloadStatusDownloading>(&status)) {
|
||||
m_enabledStatusLabel->setString(fmt::format("Downloading {}%", d->percentage).c_str());
|
||||
m_enabledStatusLabel->setColor(ccWHITE);
|
||||
m_enabledStatusLabel->setVisible(true);
|
||||
// todo: progress bar
|
||||
}
|
||||
else {
|
||||
m_enabledStatusLabel->setString("Preparing");
|
||||
m_enabledStatusLabel->setColor(ccWHITE);
|
||||
m_enabledStatusLabel->setVisible(true);
|
||||
// todo: spinner
|
||||
}
|
||||
}
|
||||
else {
|
||||
std::visit(makeVisitor {
|
||||
[this](server::DownloadStatusError const& e) {
|
||||
m_enabledStatusLabel->setString("Error");
|
||||
m_enabledStatusLabel->setColor(to3B(ColorProvider::get()->color("mod-list-disabled"_spr)));
|
||||
m_enabledStatusLabel->setVisible(true);
|
||||
// todo: show error details somewhere (like an info button)
|
||||
},
|
||||
[this](server::DownloadStatusCancelled const&) {
|
||||
m_enabledStatusLabel->setString("Cancelled");
|
||||
m_enabledStatusLabel->setColor(to3B(ColorProvider::get()->color("mod-list-disabled"_spr)));
|
||||
m_enabledStatusLabel->setVisible(true);
|
||||
},
|
||||
[this](server::DownloadStatusDone const&) {
|
||||
m_enableBtn->setVisible(false);
|
||||
m_reenableBtn->setVisible(false);
|
||||
m_updateBtn->setVisible(false);
|
||||
m_installBtn->setVisible(false);
|
||||
m_uninstallBtn->setVisible(false);
|
||||
m_cancelBtn->setVisible(false);
|
||||
|
||||
m_installStatusLabel->setString("Mod has been installed");
|
||||
m_installStatusLabel->setVisible(true);
|
||||
},
|
||||
// rest are unreachable due to the isActive() check
|
||||
[this](auto const&) {},
|
||||
}, download->getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
m_installMenu->updateLayout();
|
||||
}
|
||||
|
||||
|
@ -765,6 +835,11 @@ void ModPopup::onEnable(CCObject*) {
|
|||
UpdateModListStateEvent(UpdateModState(m_source.getID())).post();
|
||||
}
|
||||
|
||||
void ModPopup::onInstall(CCObject*) {
|
||||
server::ModDownloadManager::get()->startDownload(m_source.getID(), std::nullopt);
|
||||
this->onClose(nullptr);
|
||||
}
|
||||
|
||||
void ModPopup::onUninstall(CCObject*) {
|
||||
if (auto mod = m_source.asMod()) {
|
||||
ConfirmUninstallPopup::create(mod)->show();
|
||||
|
@ -778,6 +853,13 @@ void ModPopup::onUninstall(CCObject*) {
|
|||
}
|
||||
}
|
||||
|
||||
void ModPopup::onCancelDownload(CCObject*) {
|
||||
auto download = server::ModDownloadManager::get()->getDownload(m_source.getID());
|
||||
if (download) {
|
||||
download->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void ModPopup::onLink(CCObject* sender) {
|
||||
auto url = static_cast<CCString*>(static_cast<CCNode*>(sender)->getUserObject("url"));
|
||||
web::openLinkInBrowser(url->getCString());
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../sources/ModSource.hpp"
|
||||
#include "../GeodeStyle.hpp"
|
||||
#include "../UpdateModListState.hpp"
|
||||
#include <server/DownloadManager.hpp>
|
||||
|
||||
using namespace geode::prelude;
|
||||
|
||||
|
@ -25,6 +26,7 @@ protected:
|
|||
CCMenuItemSpriteExtra* m_uninstallBtn;
|
||||
CCMenuItemSpriteExtra* m_installBtn;
|
||||
CCMenuItemSpriteExtra* m_updateBtn;
|
||||
CCMenuItemSpriteExtra* m_cancelBtn;
|
||||
CCLabelBMFont* m_installStatusLabel;
|
||||
CCScale9Sprite* m_installBG;
|
||||
CCLabelBMFont* m_enabledStatusLabel;
|
||||
|
@ -36,6 +38,7 @@ protected:
|
|||
EventListener<server::ServerRequest<std::unordered_set<std::string>>> m_tagsListener;
|
||||
EventListener<server::ServerRequest<std::optional<server::ServerModUpdate>>> m_checkUpdateListener;
|
||||
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||
EventListener<server::ModDownloadFilter> m_downloadListener;
|
||||
|
||||
bool setup(ModSource&& src) override;
|
||||
void updateState();
|
||||
|
@ -51,7 +54,9 @@ protected:
|
|||
void loadTab(Tab tab);
|
||||
void onTab(CCObject* sender);
|
||||
void onEnable(CCObject*);
|
||||
void onInstall(CCObject*);
|
||||
void onUninstall(CCObject*);
|
||||
void onCancelDownload(CCObject*);
|
||||
|
||||
void onLink(CCObject*);
|
||||
void onSupport(CCObject*);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "ModListSource.hpp"
|
||||
#include <server/DownloadManager.hpp>
|
||||
|
||||
#define FTS_FUZZY_MATCH_IMPLEMENTATION
|
||||
#include <Geode/external/fts/fts_fuzzy_match.h>
|
||||
|
@ -250,15 +251,6 @@ InvalidateQueryAfter<InstalledModsQuery> InstalledModListSource::getQueryMut() {
|
|||
return InvalidateQueryAfter(m_query, this);
|
||||
}
|
||||
|
||||
bool InstalledModListSource::wantsRestart() const {
|
||||
for (auto mod : Loader::get()->getAllMods()) {
|
||||
if (mod->getRequestedAction() != ModRequestedAction::None) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ServerModListSource::resetQuery() {
|
||||
switch (m_type) {
|
||||
case ServerModListType::Download: {
|
||||
|
@ -357,11 +349,6 @@ InvalidateQueryAfter<server::ModsQuery> ServerModListSource::getQueryMut() {
|
|||
return InvalidateQueryAfter(m_query, this);
|
||||
}
|
||||
|
||||
bool ServerModListSource::wantsRestart() const {
|
||||
// todo
|
||||
return false;
|
||||
}
|
||||
|
||||
void ModPackListSource::resetQuery() {}
|
||||
ModPackListSource::ProviderTask ModPackListSource::fetchPage(size_t page, size_t pageSize, bool forceUpdate) {
|
||||
return ProviderTask::immediate(Err(LoadPageError("Coming soon ;)")));
|
||||
|
@ -381,10 +368,6 @@ std::unordered_set<std::string> ModPackListSource::getModTags() const {
|
|||
}
|
||||
void ModPackListSource::setModTags(std::unordered_set<std::string> const& set) {}
|
||||
|
||||
bool ModPackListSource::wantsRestart() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void clearAllModListSourceCaches() {
|
||||
InstalledModListSource::get(false)->clearCache();
|
||||
InstalledModListSource::get(true)->clearCache();
|
||||
|
@ -396,3 +379,14 @@ void clearAllModListSourceCaches() {
|
|||
|
||||
ModPackListSource::get()->clearCache();
|
||||
}
|
||||
bool isRestartRequired() {
|
||||
for (auto mod : Loader::get()->getAllMods()) {
|
||||
if (mod->getRequestedAction() != ModRequestedAction::None) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (server::ModDownloadManager::get()->wantsRestart()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -81,11 +81,6 @@ public:
|
|||
PageLoadTask loadPage(size_t page, bool forceUpdate = false);
|
||||
std::optional<size_t> getPageCount() const;
|
||||
std::optional<size_t> getItemCount() const;
|
||||
|
||||
/**
|
||||
* True if the source consists only of installed mods
|
||||
*/
|
||||
virtual bool wantsRestart() const = 0;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
|
@ -123,8 +118,6 @@ public:
|
|||
|
||||
InstalledModsQuery const& getQuery() const;
|
||||
InvalidateQueryAfter<InstalledModsQuery> getQueryMut();
|
||||
|
||||
bool wantsRestart() const override;
|
||||
};
|
||||
|
||||
enum class ServerModListType {
|
||||
|
@ -153,8 +146,6 @@ public:
|
|||
|
||||
server::ModsQuery const& getQuery() const;
|
||||
InvalidateQueryAfter<server::ModsQuery> getQueryMut();
|
||||
|
||||
bool wantsRestart() const override;
|
||||
};
|
||||
|
||||
class ModPackListSource : public ModListSource {
|
||||
|
@ -170,8 +161,7 @@ public:
|
|||
|
||||
std::unordered_set<std::string> getModTags() const override;
|
||||
void setModTags(std::unordered_set<std::string> const& tags) override;
|
||||
|
||||
bool wantsRestart() const override;
|
||||
};
|
||||
|
||||
void clearAllModListSourceCaches();
|
||||
bool isRestartRequired();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "ModSource.hpp"
|
||||
#include <Geode/ui/GeodeUI.hpp>
|
||||
#include <server/DownloadManager.hpp>
|
||||
|
||||
ModSource::ModSource(Mod* mod) : m_value(mod) {}
|
||||
ModSource::ModSource(server::ServerModMetadata&& metadata) : m_value(metadata) {}
|
||||
|
@ -37,12 +38,16 @@ CCNode* ModSource::createModLogo() const {
|
|||
}, m_value);
|
||||
}
|
||||
bool ModSource::wantsRestart() const {
|
||||
// If some download has been done for this mod, always want a restart
|
||||
auto download = server::ModDownloadManager::get()->getDownload(this->getID());
|
||||
if (download && download->isDone()) {
|
||||
return true;
|
||||
}
|
||||
return std::visit(makeVisitor {
|
||||
[](Mod* mod) {
|
||||
return mod->getRequestedAction() != ModRequestedAction::None;
|
||||
},
|
||||
[](server::ServerModMetadata const& metdata) {
|
||||
// todo: check if the mod has been installed
|
||||
return false;
|
||||
}
|
||||
}, m_value);
|
||||
|
|
Loading…
Add table
Reference in a new issue