mirror of
https://github.com/geode-sdk/geode.git
synced 2024-12-02 12:17:10 -05:00
update index to be pimpl
also remove the ability for multiple sources from it
This commit is contained in:
parent
0236b02ea1
commit
7a0ade2bf6
7 changed files with 407 additions and 464 deletions
|
@ -8,17 +8,63 @@
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace geode {
|
namespace geode {
|
||||||
|
class Index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status signifying an index-related download has been finished
|
||||||
|
*/
|
||||||
using UpdateFinished = std::monostate;
|
using UpdateFinished = std::monostate;
|
||||||
|
/**
|
||||||
|
* Status signifying an index-related download is in progress. First element
|
||||||
|
* in pair is percentage downloaded, second is status string
|
||||||
|
*/
|
||||||
using UpdateProgress = std::pair<uint8_t, std::string>;
|
using UpdateProgress = std::pair<uint8_t, std::string>;
|
||||||
|
/**
|
||||||
|
* Status signifying an index-related download has failed. Consists of the
|
||||||
|
* error string
|
||||||
|
*/
|
||||||
using UpdateFailed = std::string;
|
using UpdateFailed = std::string;
|
||||||
|
/**
|
||||||
|
* Status code for an index-related download
|
||||||
|
*/
|
||||||
using UpdateStatus = std::variant<UpdateFinished, UpdateProgress, UpdateFailed>;
|
using UpdateStatus = std::variant<UpdateFinished, UpdateProgress, UpdateFailed>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event for when a mod is being installed from the index. Automatically
|
||||||
|
* broadcast by the mods index; use ModInstallFilter to listen to these
|
||||||
|
* events
|
||||||
|
*/
|
||||||
struct GEODE_DLL ModInstallEvent : public Event {
|
struct GEODE_DLL ModInstallEvent : public Event {
|
||||||
|
/**
|
||||||
|
* The ID of the mod being installed
|
||||||
|
*/
|
||||||
const std::string modID;
|
const std::string modID;
|
||||||
|
/**
|
||||||
|
* The current status of the installation
|
||||||
|
*/
|
||||||
const UpdateStatus status;
|
const UpdateStatus status;
|
||||||
|
|
||||||
|
private:
|
||||||
ModInstallEvent(std::string const& id, const UpdateStatus status);
|
ModInstallEvent(std::string const& id, const UpdateStatus status);
|
||||||
|
|
||||||
|
friend class Index;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic filter for listening to mod installation events. Always propagates
|
||||||
|
* the event down the chain
|
||||||
|
* @example
|
||||||
|
* // Install "steve.hotdogs" and listen for its installation progress
|
||||||
|
*
|
||||||
|
* // Create a listener that listens for when steve.hotdogs is being installed
|
||||||
|
* auto listener = EventListener<ModInstallFilter>(+[](ModInstallEvent* ev) {
|
||||||
|
* // Check the event status using std::visit or other
|
||||||
|
* }, ModInstallFilter("steve.hotdogs"));
|
||||||
|
* // Get the latest version of steve.hotdogs from the index and install it
|
||||||
|
* if (auto mod = Index::get()->getMajorItem("steve.hotdogs")) {
|
||||||
|
* Index::get()->install(mod);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
class GEODE_DLL ModInstallFilter : public EventFilter<ModInstallEvent> {
|
class GEODE_DLL ModInstallFilter : public EventFilter<ModInstallEvent> {
|
||||||
protected:
|
protected:
|
||||||
std::string m_id;
|
std::string m_id;
|
||||||
|
@ -31,11 +77,18 @@ namespace geode {
|
||||||
ModInstallFilter(ModInstallFilter const&) = default;
|
ModInstallFilter(ModInstallFilter const&) = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event broadcast when the index is being updated
|
||||||
|
*/
|
||||||
struct GEODE_DLL IndexUpdateEvent : public Event {
|
struct GEODE_DLL IndexUpdateEvent : public Event {
|
||||||
const UpdateStatus status;
|
const UpdateStatus status;
|
||||||
IndexUpdateEvent(const UpdateStatus status);
|
IndexUpdateEvent(const UpdateStatus status);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic filter for listening to index update events. Always propagates
|
||||||
|
* the event down the chain
|
||||||
|
*/
|
||||||
class GEODE_DLL IndexUpdateFilter : public EventFilter<IndexUpdateEvent> {
|
class GEODE_DLL IndexUpdateFilter : public EventFilter<IndexUpdateEvent> {
|
||||||
public:
|
public:
|
||||||
using Callback = void(IndexUpdateEvent*);
|
using Callback = void(IndexUpdateEvent*);
|
||||||
|
@ -45,32 +98,24 @@ namespace geode {
|
||||||
IndexUpdateFilter(IndexUpdateFilter const&) = default;
|
IndexUpdateFilter(IndexUpdateFilter const&) = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IndexSourceImpl;
|
class GEODE_DLL IndexItem final {
|
||||||
struct GEODE_DLL IndexSourceImplDeleter {
|
public:
|
||||||
void operator()(IndexSourceImpl* src);
|
class Impl;
|
||||||
};
|
|
||||||
struct SourceUpdateEvent;
|
|
||||||
using IndexSourcePtr = std::unique_ptr<IndexSourceImpl, IndexSourceImplDeleter>;
|
|
||||||
|
|
||||||
struct GEODE_DLL IndexItem {
|
private:
|
||||||
std::string sourceRepository;
|
std::unique_ptr<Impl> m_impl;
|
||||||
ghc::filesystem::path path;
|
|
||||||
ModInfo info;
|
|
||||||
struct {
|
|
||||||
std::string url;
|
|
||||||
std::string hash;
|
|
||||||
std::unordered_set<PlatformID> platforms;
|
|
||||||
} download;
|
|
||||||
bool isFeatured;
|
|
||||||
std::unordered_set<std::string> tags;
|
|
||||||
|
|
||||||
/**
|
public:
|
||||||
* Create IndexItem from a directory
|
ghc::filesystem::path getPath() const;
|
||||||
*/
|
ModInfo getModInfo() const;
|
||||||
static Result<std::shared_ptr<IndexItem>> createFromDir(
|
std::string getDownloadURL() const;
|
||||||
std::string const& sourceRepository,
|
std::string getPackageHash() const;
|
||||||
ghc::filesystem::path const& dir
|
std::unordered_set<PlatformID> getAvailablePlatforms() const;
|
||||||
);
|
bool isFeatured() const;
|
||||||
|
std::unordered_set<std::string> getTags() const;
|
||||||
|
|
||||||
|
IndexItem();
|
||||||
|
~IndexItem();
|
||||||
};
|
};
|
||||||
using IndexItemHandle = std::shared_ptr<IndexItem>;
|
using IndexItemHandle = std::shared_ptr<IndexItem>;
|
||||||
|
|
||||||
|
@ -85,38 +130,19 @@ namespace geode {
|
||||||
std::vector<IndexItemHandle> list;
|
std::vector<IndexItemHandle> list;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GEODE_DLL Index final {
|
static constexpr size_t MAX_INDEX_API_VERSION = 0;
|
||||||
protected:
|
|
||||||
// for once, the fact that std::map is ordered is useful (this makes
|
|
||||||
// getting the latest version of a mod as easy as items.rbegin())
|
|
||||||
using ItemVersions = std::map<size_t, IndexItemHandle>;
|
|
||||||
|
|
||||||
std::vector<IndexSourcePtr> m_sources;
|
class GEODE_DLL Index final {
|
||||||
std::unordered_map<std::string, UpdateStatus> m_sourceStatuses;
|
private:
|
||||||
std::unordered_map<
|
class Impl;
|
||||||
IndexItemHandle,
|
std::unique_ptr<Impl> m_impl;
|
||||||
utils::web::SentAsyncWebRequestHandle
|
|
||||||
> m_runningInstallations;
|
|
||||||
std::atomic<bool> m_triedToUpdate = false;
|
|
||||||
std::unordered_map<std::string, ItemVersions> m_items;
|
|
||||||
|
|
||||||
Index();
|
Index();
|
||||||
|
~Index();
|
||||||
void onSourceUpdate(SourceUpdateEvent* event);
|
|
||||||
void checkSourceUpdates(IndexSourceImpl* src);
|
|
||||||
void downloadSource(IndexSourceImpl* src);
|
|
||||||
void updateSourceFromLocal(IndexSourceImpl* src);
|
|
||||||
void cleanupItems();
|
|
||||||
|
|
||||||
void installNext(size_t index, IndexInstallList const& list);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Index* get();
|
static Index* get();
|
||||||
|
|
||||||
void addSource(std::string const& repository);
|
|
||||||
void removeSource(std::string const& repository);
|
|
||||||
std::vector<std::string> getSources() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all tags
|
* Get all tags
|
||||||
*/
|
*/
|
||||||
|
@ -206,13 +232,17 @@ namespace geode {
|
||||||
Result<IndexInstallList> getInstallList(IndexItemHandle item) const;
|
Result<IndexInstallList> getInstallList(IndexItemHandle item) const;
|
||||||
/**
|
/**
|
||||||
* Install an index item. Add an event listener for the ModInstallEvent
|
* Install an index item. Add an event listener for the ModInstallEvent
|
||||||
* class to track the installation progress
|
* class to track the installation progress. Automatically also downloads
|
||||||
|
* all missing dependencies for the item
|
||||||
* @param item Item to install
|
* @param item Item to install
|
||||||
*/
|
*/
|
||||||
void install(IndexItemHandle item);
|
void install(IndexItemHandle item);
|
||||||
/**
|
/**
|
||||||
* Install a list of index items. Add an event listener for the
|
* Install a list of index items. Add an event listener for the
|
||||||
* ModInstallEvent class to track the installation progress
|
* ModInstallEvent class to track the installation progress
|
||||||
|
* @warning Does not download any missing dependencies - use the
|
||||||
|
* `install(IndexItemHandle)` overload if you aren't sure all the
|
||||||
|
* dependencies are installed!
|
||||||
* @param list List of items to install
|
* @param list List of items to install
|
||||||
*/
|
*/
|
||||||
void install(IndexInstallList const& list);
|
void install(IndexInstallList const& list);
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include <hash/hash.hpp>
|
#include <hash/hash.hpp>
|
||||||
#include <Geode/utils/JsonValidation.hpp>
|
#include <Geode/utils/JsonValidation.hpp>
|
||||||
|
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
// ModInstallEvent
|
// ModInstallEvent
|
||||||
|
@ -26,54 +25,6 @@ ListenerResult ModInstallFilter::handle(utils::MiniFunction<Callback> fn, ModIns
|
||||||
|
|
||||||
ModInstallFilter::ModInstallFilter(std::string const& id) : m_id(id) {}
|
ModInstallFilter::ModInstallFilter(std::string const& id) : m_id(id) {}
|
||||||
|
|
||||||
// IndexUpdateEvent implementation
|
|
||||||
|
|
||||||
// The reason sources have private implementation events that are
|
|
||||||
// turned into the global IndexUpdateEvent is because it makes it much
|
|
||||||
// simpler to keep track of progress, what errors were received, etc.
|
|
||||||
// without having to store a ton of members
|
|
||||||
|
|
||||||
struct geode::IndexSourceImpl final {
|
|
||||||
std::string repository;
|
|
||||||
bool isUpToDate = false;
|
|
||||||
|
|
||||||
std::string dirname() const {
|
|
||||||
return string::replace(this->repository, "/", "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
ghc::filesystem::path path() const {
|
|
||||||
return dirs::getIndexDir() / this->dirname();
|
|
||||||
}
|
|
||||||
|
|
||||||
ghc::filesystem::path checksum() const {
|
|
||||||
// not storing this in the source's directory as that gets replaced by
|
|
||||||
// the newly fetched index
|
|
||||||
return dirs::getIndexDir() / (this->dirname() + ".checksum");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void IndexSourceImplDeleter::operator()(IndexSourceImpl* src) {
|
|
||||||
delete src;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct geode::SourceUpdateEvent : public Event {
|
|
||||||
IndexSourceImpl* source;
|
|
||||||
const UpdateStatus status;
|
|
||||||
SourceUpdateEvent(IndexSourceImpl* src, const UpdateStatus status)
|
|
||||||
: source(src), status(status) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class SourceUpdateFilter : public EventFilter<SourceUpdateEvent> {
|
|
||||||
public:
|
|
||||||
using Callback = void(SourceUpdateEvent*);
|
|
||||||
|
|
||||||
ListenerResult handle(utils::MiniFunction<Callback> fn, SourceUpdateEvent* event) {
|
|
||||||
fn(event);
|
|
||||||
return ListenerResult::Propagate;
|
|
||||||
}
|
|
||||||
SourceUpdateFilter() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// IndexUpdateEvent
|
// IndexUpdateEvent
|
||||||
|
|
||||||
IndexUpdateEvent::IndexUpdateEvent(const UpdateStatus status) : status(status) {}
|
IndexUpdateEvent::IndexUpdateEvent(const UpdateStatus status) : status(status) {}
|
||||||
|
@ -90,10 +41,59 @@ IndexUpdateFilter::IndexUpdateFilter() {}
|
||||||
|
|
||||||
// IndexItem
|
// IndexItem
|
||||||
|
|
||||||
Result<IndexItemHandle> IndexItem::createFromDir(
|
class IndexItem::Impl final {
|
||||||
std::string const& sourceRepository,
|
private:
|
||||||
|
ghc::filesystem::path m_path;
|
||||||
|
ModInfo m_info;
|
||||||
|
std::string m_downloadURL;
|
||||||
|
std::string m_downloadHash;
|
||||||
|
std::unordered_set<PlatformID> m_platforms;
|
||||||
|
bool m_isFeatured;
|
||||||
|
std::unordered_set<std::string> m_tags;
|
||||||
|
|
||||||
|
friend class IndexItem;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Create IndexItem from a directory
|
||||||
|
*/
|
||||||
|
static Result<std::shared_ptr<IndexItem>> create(
|
||||||
ghc::filesystem::path const& dir
|
ghc::filesystem::path const& dir
|
||||||
) {
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
IndexItem::IndexItem() : m_impl(std::make_unique<Impl>()) {}
|
||||||
|
IndexItem::~IndexItem() = default;
|
||||||
|
|
||||||
|
ghc::filesystem::path IndexItem::getPath() const {
|
||||||
|
return m_impl->m_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModInfo IndexItem::getModInfo() const {
|
||||||
|
return m_impl->m_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IndexItem::getDownloadURL() const {
|
||||||
|
return m_impl->m_downloadURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IndexItem::getPackageHash() const {
|
||||||
|
return m_impl->m_downloadHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<PlatformID> IndexItem::getAvailablePlatforms() const {
|
||||||
|
return m_impl->m_platforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IndexItem::isFeatured() const {
|
||||||
|
return m_impl->m_isFeatured;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<std::string> IndexItem::getTags() const {
|
||||||
|
return m_impl->m_tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& dir) {
|
||||||
GEODE_UNWRAP_INTO(
|
GEODE_UNWRAP_INTO(
|
||||||
auto entry, file::readJson(dir / "entry.json")
|
auto entry, file::readJson(dir / "entry.json")
|
||||||
.expect("Unable to read entry.json")
|
.expect("Unable to read entry.json")
|
||||||
|
@ -111,18 +111,15 @@ Result<IndexItemHandle> IndexItem::createFromDir(
|
||||||
platforms.insert(PlatformID::from(plat.template get<std::string>()));
|
platforms.insert(PlatformID::from(plat.template get<std::string>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto item = std::make_shared<IndexItem>(IndexItem {
|
auto item = std::make_shared<IndexItem>();
|
||||||
.sourceRepository = sourceRepository,
|
item->m_impl->m_path = dir;
|
||||||
.path = dir,
|
item->m_impl->m_info = info;
|
||||||
.info = info,
|
item->m_impl->m_downloadURL = root.has("mod").obj().has("download").template get<std::string>();
|
||||||
.download = {
|
item->m_impl->m_downloadHash = root.has("mod").obj().has("hash").template get<std::string>();
|
||||||
.url = root.has("mod").obj().has("download").template get<std::string>(),
|
item->m_impl->m_platforms = platforms;
|
||||||
.hash = root.has("mod").obj().has("hash").template get<std::string>(),
|
item->m_impl->m_isFeatured = root.has("featured").template get<bool>();
|
||||||
.platforms = platforms,
|
item->m_impl->m_tags = root.has("tags").template get<std::unordered_set<std::string>>();
|
||||||
},
|
|
||||||
.isFeatured = root.has("featured").template get<bool>(),
|
|
||||||
.tags = root.has("tags").template get<std::unordered_set<std::string>>()
|
|
||||||
});
|
|
||||||
if (checker.isError()) {
|
if (checker.isError()) {
|
||||||
return Err(checker.getError());
|
return Err(checker.getError());
|
||||||
}
|
}
|
||||||
|
@ -152,263 +149,53 @@ static Result<> flattenGithubRepo(ghc::filesystem::path const& dir) {
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Index impl
|
||||||
|
|
||||||
|
class Index::Impl final {
|
||||||
|
public:
|
||||||
|
// for once, the fact that std::map is ordered is useful (this makes
|
||||||
|
// getting the latest version of a mod as easy as items.rbegin())
|
||||||
|
using ItemVersions = std::map<size_t, IndexItemHandle>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<
|
||||||
|
IndexItemHandle,
|
||||||
|
utils::web::SentAsyncWebRequestHandle
|
||||||
|
> m_runningInstallations;
|
||||||
|
std::atomic<bool> m_isUpToDate = false;
|
||||||
|
std::atomic<bool> m_updating = false;
|
||||||
|
std::atomic<bool> m_triedToUpdate = false;
|
||||||
|
std::unordered_map<std::string, ItemVersions> m_items;
|
||||||
|
|
||||||
|
friend class Index;
|
||||||
|
|
||||||
|
void cleanupItems();
|
||||||
|
void downloadIndex();
|
||||||
|
void checkForUpdates();
|
||||||
|
void updateFromLocalTree();
|
||||||
|
void installNext(size_t index, IndexInstallList const& list);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Impl() {
|
||||||
|
new EventListener<IndexUpdateFilter>([this](IndexUpdateEvent* ev) {
|
||||||
|
m_updating = std::holds_alternative<UpdateProgress>(ev->status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Index globals
|
// Index globals
|
||||||
|
|
||||||
Index::Index() {
|
Index::Index() : m_impl(std::make_unique<Impl>()) {}
|
||||||
new EventListener(
|
Index::~Index() = default;
|
||||||
std::bind(&Index::onSourceUpdate, this, std::placeholders::_1),
|
|
||||||
SourceUpdateFilter()
|
|
||||||
);
|
|
||||||
this->addSource("geode-sdk/mods");
|
|
||||||
}
|
|
||||||
|
|
||||||
Index* Index::get() {
|
Index* Index::get() {
|
||||||
static auto inst = new Index();
|
static auto inst = new Index();
|
||||||
return inst;
|
return inst;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sources
|
|
||||||
|
|
||||||
void Index::addSource(std::string const& repository) {
|
|
||||||
m_sources.emplace_back(new IndexSourceImpl {
|
|
||||||
.repository = repository
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Index::removeSource(std::string const& repository) {
|
|
||||||
ranges::remove(m_sources, [repository](IndexSourcePtr const& src) {
|
|
||||||
return src->repository == repository;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> Index::getSources() const {
|
|
||||||
std::vector<std::string> res;
|
|
||||||
for (auto& src : m_sources) {
|
|
||||||
res.push_back(src->repository);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updating
|
// Updating
|
||||||
|
|
||||||
void Index::onSourceUpdate(SourceUpdateEvent* event) {
|
void Index::Impl::cleanupItems() {
|
||||||
// save status for aggregating SourceUpdateEvents to a single global
|
|
||||||
// IndexUpdateEvent
|
|
||||||
m_sourceStatuses[event->source->repository] = event->status;
|
|
||||||
|
|
||||||
// figure out aggregate event
|
|
||||||
enum { Finished, Progress, Failed, } whatToPost = Finished;
|
|
||||||
for (auto& [src, status] : m_sourceStatuses) {
|
|
||||||
// if some source is still updating, post progress
|
|
||||||
if (std::holds_alternative<UpdateProgress>(status)) {
|
|
||||||
whatToPost = Progress;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// otherwise, if some source failed, then post failed
|
|
||||||
else if (std::holds_alternative<UpdateFailed>(status)) {
|
|
||||||
if (whatToPost != Progress) {
|
|
||||||
whatToPost = Failed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// otherwise if all are finished, whatToPost is already set to that
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (whatToPost) {
|
|
||||||
case Finished: {
|
|
||||||
log::debug("Index up-to-date");
|
|
||||||
// clear source statuses to allow updating index again
|
|
||||||
m_sourceStatuses.clear();
|
|
||||||
// post finish event
|
|
||||||
IndexUpdateEvent(UpdateFinished()).post();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case Progress: {
|
|
||||||
// get total progress
|
|
||||||
size_t total = 0;
|
|
||||||
for (auto& [src, status] : m_sourceStatuses) {
|
|
||||||
if (std::holds_alternative<UpdateProgress>(status)) {
|
|
||||||
total += std::get<UpdateProgress>(status).first;
|
|
||||||
} else {
|
|
||||||
total += 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IndexUpdateEvent(
|
|
||||||
UpdateProgress(
|
|
||||||
static_cast<uint8_t>(total / m_sourceStatuses.size()),
|
|
||||||
"Downloading"
|
|
||||||
)
|
|
||||||
).post();
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case Failed: {
|
|
||||||
std::string info = "";
|
|
||||||
for (auto& [src, status] : m_sourceStatuses) {
|
|
||||||
if (std::holds_alternative<UpdateFailed>(status)) {
|
|
||||||
info += src + ": " + std::get<UpdateFailed>(status) + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log::debug("Index update failed: {}", info);
|
|
||||||
// clear source statuses to allow updating index again
|
|
||||||
m_sourceStatuses.clear();
|
|
||||||
// post finish event
|
|
||||||
IndexUpdateEvent(UpdateFailed(info)).post();
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Index::checkSourceUpdates(IndexSourceImpl* src) {
|
|
||||||
if (src->isUpToDate) {
|
|
||||||
return this->updateSourceFromLocal(src);
|
|
||||||
}
|
|
||||||
|
|
||||||
log::debug("Checking updates for source {}", src->repository);
|
|
||||||
SourceUpdateEvent(src, UpdateProgress(0, "Checking status")).post();
|
|
||||||
|
|
||||||
// read old commit SHA
|
|
||||||
// not using saved values for this one as we don't want to refetch
|
|
||||||
// index even if the game crashes
|
|
||||||
auto oldSHA = file::readString(src->checksum()).unwrapOr("");
|
|
||||||
web::AsyncWebRequest()
|
|
||||||
.join(fmt::format("index-update-{}", src->repository))
|
|
||||||
.header(fmt::format("If-None-Match: \"{}\"", oldSHA))
|
|
||||||
.header("Accept: application/vnd.github.sha")
|
|
||||||
.fetch(fmt::format("https://api.github.com/repos/{}/commits/main", src->repository))
|
|
||||||
.text()
|
|
||||||
.then([this, src, oldSHA](std::string const& newSHA) {
|
|
||||||
// check if should just be updated from local cache
|
|
||||||
if (
|
|
||||||
// if no new hash was given (rate limited) or the new hash is the
|
|
||||||
// same as old
|
|
||||||
(newSHA.empty() || oldSHA == newSHA) &&
|
|
||||||
// make sure the downloaded local copy actually exists
|
|
||||||
ghc::filesystem::exists(src->path()) &&
|
|
||||||
ghc::filesystem::exists(src->path() / "config.json")
|
|
||||||
) {
|
|
||||||
this->updateSourceFromLocal(src);
|
|
||||||
}
|
|
||||||
// otherwise save hash and download source
|
|
||||||
else {
|
|
||||||
(void)file::writeString(src->checksum(), newSHA);
|
|
||||||
this->downloadSource(src);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.expect([src](std::string const& err) {
|
|
||||||
SourceUpdateEvent(
|
|
||||||
src,
|
|
||||||
UpdateFailed(fmt::format("Error checking for updates: {}", err))
|
|
||||||
).post();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Index::downloadSource(IndexSourceImpl* src) {
|
|
||||||
log::debug("Downloading source {}", src->repository);
|
|
||||||
|
|
||||||
SourceUpdateEvent(src, UpdateProgress(0, "Beginning download")).post();
|
|
||||||
|
|
||||||
auto targetFile = dirs::getIndexDir() / fmt::format("{}.zip", src->dirname());
|
|
||||||
|
|
||||||
web::AsyncWebRequest()
|
|
||||||
.join(fmt::format("index-download-{}", src->repository))
|
|
||||||
.fetch(fmt::format("https://github.com/{}/zipball/main", src->repository))
|
|
||||||
.into(targetFile)
|
|
||||||
.then([this, src, targetFile](auto) {
|
|
||||||
auto targetDir = src->path();
|
|
||||||
// delete old unzipped index
|
|
||||||
try {
|
|
||||||
if (ghc::filesystem::exists(targetDir)) {
|
|
||||||
ghc::filesystem::remove_all(targetDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(...) {
|
|
||||||
SourceUpdateEvent(
|
|
||||||
src, UpdateFailed("Unable to clear cached index")
|
|
||||||
).post();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// unzip new index
|
|
||||||
auto unzip = file::Unzip::intoDir(targetFile, targetDir, true)
|
|
||||||
.expect("Unable to unzip new index");
|
|
||||||
if (!unzip) {
|
|
||||||
SourceUpdateEvent(
|
|
||||||
src, UpdateFailed(unzip.unwrapErr())
|
|
||||||
).post();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the directory github adds to the root of the zip
|
|
||||||
(void)flattenGithubRepo(targetDir);
|
|
||||||
|
|
||||||
// update index
|
|
||||||
this->updateSourceFromLocal(src);
|
|
||||||
})
|
|
||||||
.expect([src](std::string const& err) {
|
|
||||||
SourceUpdateEvent(
|
|
||||||
src, UpdateFailed(fmt::format("Error downloading: {}", err))
|
|
||||||
).post();
|
|
||||||
})
|
|
||||||
.progress([src](auto&, double now, double total) {
|
|
||||||
SourceUpdateEvent(
|
|
||||||
src,
|
|
||||||
UpdateProgress(
|
|
||||||
static_cast<uint8_t>(now / total * 100.0),
|
|
||||||
"Downloading"
|
|
||||||
)
|
|
||||||
).post();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Index::updateSourceFromLocal(IndexSourceImpl* src) {
|
|
||||||
log::debug("Updating local cache for source {}", src->repository);
|
|
||||||
SourceUpdateEvent(src, UpdateProgress(100, "Updating local cache")).post();
|
|
||||||
// delete old items from this url if such exist
|
|
||||||
for (auto& [_, versions] : m_items) {
|
|
||||||
for (auto it = versions.begin(); it != versions.end(); ) {
|
|
||||||
if (it->second->sourceRepository == src->repository) {
|
|
||||||
it = versions.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this->cleanupItems();
|
|
||||||
|
|
||||||
// read directory and add new items
|
|
||||||
try {
|
|
||||||
for (auto& dir : ghc::filesystem::directory_iterator(src->path() / "mods")) {
|
|
||||||
auto addRes = IndexItem::createFromDir(src->repository, dir);
|
|
||||||
if (!addRes) {
|
|
||||||
log::warn("Unable to add index item from {}: {}", dir, addRes.unwrapErr());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto add = addRes.unwrap();
|
|
||||||
// check if this major version of this item has already been added
|
|
||||||
if (m_items[add->info.id()].count(add->info.version().getMajor())) {
|
|
||||||
log::warn(
|
|
||||||
"Item {}@{} has already been added, skipping",
|
|
||||||
add->info.id(), add->info.version()
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// add new major version of this item
|
|
||||||
m_items[add->info.id()].insert({
|
|
||||||
add->info.version().getMajor(),
|
|
||||||
add
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch(std::exception& e) {
|
|
||||||
SourceUpdateEvent(src, fmt::format(
|
|
||||||
"Unable to read source {}", src->repository
|
|
||||||
)).post();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark source as finished
|
|
||||||
src->isUpToDate = true;
|
|
||||||
SourceUpdateEvent(src, UpdateFinished()).post();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Index::cleanupItems() {
|
|
||||||
// delete mods with no versions
|
// delete mods with no versions
|
||||||
for (auto it = m_items.begin(); it != m_items.end(); ) {
|
for (auto it = m_items.begin(); it != m_items.end(); ) {
|
||||||
if (!it->second.size()) {
|
if (!it->second.size()) {
|
||||||
|
@ -420,49 +207,173 @@ void Index::cleanupItems() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Index::isUpToDate() const {
|
bool Index::isUpToDate() const {
|
||||||
for (auto& source : m_sources) {
|
return m_impl->m_isUpToDate;
|
||||||
if (!source->isUpToDate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Index::hasTriedToUpdate() const {
|
bool Index::hasTriedToUpdate() const {
|
||||||
return m_triedToUpdate;
|
return m_impl->m_triedToUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Index::Impl::downloadIndex() {
|
||||||
|
log::debug("Downloading index");
|
||||||
|
|
||||||
|
IndexUpdateEvent(UpdateProgress(0, "Beginning download")).post();
|
||||||
|
|
||||||
|
auto targetFile = dirs::getTempDir() / "updated-index.zip";
|
||||||
|
|
||||||
|
web::AsyncWebRequest()
|
||||||
|
.join("index-download")
|
||||||
|
.fetch("https://github.com/geode-sdk/mods/zipball/main")
|
||||||
|
.into(targetFile)
|
||||||
|
.then([this, targetFile](auto) {
|
||||||
|
auto targetDir = dirs::getIndexDir() / "v0";
|
||||||
|
// delete old unzipped index
|
||||||
|
try {
|
||||||
|
if (ghc::filesystem::exists(targetDir)) {
|
||||||
|
ghc::filesystem::remove_all(targetDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
IndexUpdateEvent(UpdateFailed("Unable to clear cached index")).post();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unzip new index
|
||||||
|
auto unzip = file::Unzip::intoDir(targetFile, targetDir, true)
|
||||||
|
.expect("Unable to unzip new index");
|
||||||
|
if (!unzip) {
|
||||||
|
IndexUpdateEvent(UpdateFailed(unzip.unwrapErr())).post();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the directory github adds to the root of the zip
|
||||||
|
(void)flattenGithubRepo(targetDir);
|
||||||
|
|
||||||
|
// update index
|
||||||
|
this->updateFromLocalTree();
|
||||||
|
})
|
||||||
|
.expect([](std::string const& err) {
|
||||||
|
IndexUpdateEvent(UpdateFailed(fmt::format("Error downloading: {}", err))).post();
|
||||||
|
})
|
||||||
|
.progress([](auto&, double now, double total) {
|
||||||
|
IndexUpdateEvent(
|
||||||
|
UpdateProgress(
|
||||||
|
static_cast<uint8_t>(now / total * 100.0),
|
||||||
|
"Downloading"
|
||||||
|
)
|
||||||
|
).post();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Index::Impl::checkForUpdates() {
|
||||||
|
if (m_isUpToDate) {
|
||||||
|
return this->updateFromLocalTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug("Checking updates for index");
|
||||||
|
IndexUpdateEvent(UpdateProgress(0, "Checking status")).post();
|
||||||
|
|
||||||
|
auto checksum = dirs::getIndexDir() / ".checksum";
|
||||||
|
|
||||||
|
// read old commit SHA
|
||||||
|
// not using saved values for this one as we don't want to refetch
|
||||||
|
// index even if the game crashes
|
||||||
|
auto oldSHA = file::readString(checksum).unwrapOr("");
|
||||||
|
web::AsyncWebRequest()
|
||||||
|
.join("index-update")
|
||||||
|
.header(fmt::format("If-None-Match: \"{}\"", oldSHA))
|
||||||
|
.header("Accept: application/vnd.github.sha")
|
||||||
|
.fetch("https://api.github.com/repos/geode-sdk/mods/commits/main")
|
||||||
|
.text()
|
||||||
|
.then([this, checksum, oldSHA](std::string const& newSHA) {
|
||||||
|
// check if should just be updated from local cache
|
||||||
|
if (
|
||||||
|
// if no new hash was given (rate limited) or the new hash is the
|
||||||
|
// same as old
|
||||||
|
(newSHA.empty() || oldSHA == newSHA) &&
|
||||||
|
// make sure the downloaded local copy actually exists
|
||||||
|
ghc::filesystem::exists(dirs::getIndexDir() / "v0" / "config.json")
|
||||||
|
) {
|
||||||
|
this->updateFromLocalTree();
|
||||||
|
}
|
||||||
|
// otherwise save hash and download source
|
||||||
|
else {
|
||||||
|
(void)file::writeString(checksum, newSHA);
|
||||||
|
this->downloadIndex();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.expect([](std::string const& err) {
|
||||||
|
IndexUpdateEvent(
|
||||||
|
UpdateFailed(fmt::format("Error checking for updates: {}", err))
|
||||||
|
).post();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Index::Impl::updateFromLocalTree() {
|
||||||
|
log::debug("Updating local index cache");
|
||||||
|
IndexUpdateEvent(UpdateProgress(100, "Updating local cache")).post();
|
||||||
|
// delete old items
|
||||||
|
m_items.clear();
|
||||||
|
|
||||||
|
// read directory and add new items
|
||||||
|
try {
|
||||||
|
for (auto& dir : ghc::filesystem::directory_iterator(dirs::getIndexDir() / "v0" / "mods")) {
|
||||||
|
auto addRes = IndexItem::Impl::create(dir);
|
||||||
|
if (!addRes) {
|
||||||
|
log::warn("Unable to add index item from {}: {}", dir, addRes.unwrapErr());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto add = addRes.unwrap();
|
||||||
|
auto info = add->getModInfo();
|
||||||
|
// check if this major version of this item has already been added
|
||||||
|
if (m_items[info.id()].count(info.version().getMajor())) {
|
||||||
|
log::warn(
|
||||||
|
"Item {}@{} has already been added, skipping",
|
||||||
|
info.id(), info.version()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// add new major version of this item
|
||||||
|
m_items[info.id()].insert({
|
||||||
|
info.version().getMajor(),
|
||||||
|
add
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch(std::exception& e) {
|
||||||
|
IndexUpdateEvent("Unable to read local index tree").post();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark source as finished
|
||||||
|
m_isUpToDate = true;
|
||||||
|
IndexUpdateEvent(UpdateFinished()).post();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Index::update(bool force) {
|
void Index::update(bool force) {
|
||||||
// create index dir if it doesn't exist
|
// create index dir if it doesn't exist
|
||||||
(void)file::createDirectoryAll(dirs::getIndexDir());
|
(void)file::createDirectoryAll(dirs::getIndexDir());
|
||||||
|
|
||||||
m_triedToUpdate = true;
|
m_impl->m_triedToUpdate = true;
|
||||||
|
|
||||||
// update all sources in GD thread for synchronization (m_sourceStatuses
|
// check if update is already happening
|
||||||
// and every other member access happens in AsyncWebRequest callbacks
|
if (m_impl->m_updating) {
|
||||||
// which are always run in the GD thread aswell)
|
|
||||||
Loader::get()->queueInGDThread([force, this]() {
|
|
||||||
// check if some sources are already being updated
|
|
||||||
if (m_sourceStatuses.size()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_impl->m_updating = true;
|
||||||
|
|
||||||
// update sources
|
// update sources
|
||||||
for (auto& src : m_sources) {
|
|
||||||
if (force) {
|
if (force) {
|
||||||
this->downloadSource(src.get());
|
m_impl->downloadIndex();
|
||||||
} else {
|
} else {
|
||||||
this->checkSourceUpdates(src.get());
|
m_impl->checkForUpdates();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Items
|
// Items
|
||||||
|
|
||||||
std::vector<IndexItemHandle> Index::getItems() const {
|
std::vector<IndexItemHandle> Index::getItems() const {
|
||||||
std::vector<IndexItemHandle> res;
|
std::vector<IndexItemHandle> res;
|
||||||
for (auto& items : map::values(m_items)) {
|
for (auto& items : map::values(m_impl->m_items)) {
|
||||||
for (auto& item : items) {
|
for (auto& item : items) {
|
||||||
res.push_back(item.second);
|
res.push_back(item.second);
|
||||||
}
|
}
|
||||||
|
@ -472,9 +383,9 @@ std::vector<IndexItemHandle> Index::getItems() const {
|
||||||
|
|
||||||
std::vector<IndexItemHandle> Index::getFeaturedItems() const {
|
std::vector<IndexItemHandle> Index::getFeaturedItems() const {
|
||||||
std::vector<IndexItemHandle> res;
|
std::vector<IndexItemHandle> res;
|
||||||
for (auto& items : map::values(m_items)) {
|
for (auto& items : map::values(m_impl->m_items)) {
|
||||||
for (auto& item : items) {
|
for (auto& item : items) {
|
||||||
if (item.second->isFeatured) {
|
if (item.second->isFeatured()) {
|
||||||
res.push_back(item.second);
|
res.push_back(item.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -486,9 +397,9 @@ std::vector<IndexItemHandle> Index::getItemsByDeveloper(
|
||||||
std::string const& name
|
std::string const& name
|
||||||
) const {
|
) const {
|
||||||
std::vector<IndexItemHandle> res;
|
std::vector<IndexItemHandle> res;
|
||||||
for (auto& items : map::values(m_items)) {
|
for (auto& items : map::values(m_impl->m_items)) {
|
||||||
for (auto& item : items) {
|
for (auto& item : items) {
|
||||||
if (item.second->info.developer() == name) {
|
if (item.second->getModInfo().developer() == name) {
|
||||||
res.push_back(item.second);
|
res.push_back(item.second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -506,8 +417,8 @@ bool Index::isKnownItem(
|
||||||
IndexItemHandle Index::getMajorItem(
|
IndexItemHandle Index::getMajorItem(
|
||||||
std::string const& id
|
std::string const& id
|
||||||
) const {
|
) const {
|
||||||
if (m_items.count(id)) {
|
if (m_impl->m_items.count(id)) {
|
||||||
return m_items.at(id).rbegin()->second;
|
return m_impl->m_items.at(id).rbegin()->second;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -516,18 +427,18 @@ IndexItemHandle Index::getItem(
|
||||||
std::string const& id,
|
std::string const& id,
|
||||||
std::optional<VersionInfo> version
|
std::optional<VersionInfo> version
|
||||||
) const {
|
) const {
|
||||||
if (m_items.count(id)) {
|
if (m_impl->m_items.count(id)) {
|
||||||
auto versions = m_items.at(id);
|
auto versions = m_impl->m_items.at(id);
|
||||||
if (version) {
|
if (version) {
|
||||||
// prefer most major version
|
// prefer most major version
|
||||||
for (auto& [_, item] : ranges::reverse(m_items.at(id))) {
|
for (auto& [_, item] : ranges::reverse(m_impl->m_items.at(id))) {
|
||||||
if (version.value() == item->info.version()) {
|
if (version.value() == item->getModInfo().version()) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (versions.size()) {
|
if (versions.size()) {
|
||||||
return m_items.at(id).rbegin()->second;
|
return m_impl->m_items.at(id).rbegin()->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,10 +449,10 @@ IndexItemHandle Index::getItem(
|
||||||
std::string const& id,
|
std::string const& id,
|
||||||
ComparableVersionInfo version
|
ComparableVersionInfo version
|
||||||
) const {
|
) const {
|
||||||
if (m_items.count(id)) {
|
if (m_impl->m_items.count(id)) {
|
||||||
// prefer most major version
|
// prefer most major version
|
||||||
for (auto& [_, item] : ranges::reverse(m_items.at(id))) {
|
for (auto& [_, item] : ranges::reverse(m_impl->m_items.at(id))) {
|
||||||
if (version.compare(item->info.version())) {
|
if (version.compare(item->getModInfo().version())) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -558,17 +469,17 @@ IndexItemHandle Index::getItem(Mod* mod) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Index::isUpdateAvailable(IndexItemHandle item) const {
|
bool Index::isUpdateAvailable(IndexItemHandle item) const {
|
||||||
auto installed = Loader::get()->getInstalledMod(item->info.id());
|
auto installed = Loader::get()->getInstalledMod(item->getModInfo().id());
|
||||||
if (!installed) {
|
if (!installed) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return item->info.version() > installed->getVersion();
|
return item->getModInfo().version() > installed->getVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Index::areUpdatesAvailable() const {
|
bool Index::areUpdatesAvailable() const {
|
||||||
for (auto& mod : Loader::get()->getAllMods()) {
|
for (auto& mod : Loader::get()->getAllMods()) {
|
||||||
auto item = this->getMajorItem(mod->getID());
|
auto item = this->getMajorItem(mod->getID());
|
||||||
if (item && item->info.version() > mod->getVersion()) {
|
if (item && item->getModInfo().version() > mod->getVersion()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -578,17 +489,17 @@ bool Index::areUpdatesAvailable() const {
|
||||||
// Item installation
|
// Item installation
|
||||||
|
|
||||||
Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
||||||
if (!item->download.platforms.count(GEODE_PLATFORM_TARGET)) {
|
if (!item->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) {
|
||||||
return Err("Mod is not available on {}", GEODE_PLATFORM_NAME);
|
return Err("Mod is not available on {}", GEODE_PLATFORM_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
IndexInstallList list;
|
IndexInstallList list;
|
||||||
list.target = item;
|
list.target = item;
|
||||||
for (auto& dep : item->info.dependencies()) {
|
for (auto& dep : item->getModInfo().dependencies()) {
|
||||||
if (!dep.isResolved()) {
|
if (!dep.isResolved()) {
|
||||||
// check if this dep is available in the index
|
// check if this dep is available in the index
|
||||||
if (auto depItem = this->getItem(dep.id, dep.version)) {
|
if (auto depItem = this->getItem(dep.id, dep.version)) {
|
||||||
if (!depItem->download.platforms.count(GEODE_PLATFORM_TARGET)) {
|
if (!depItem->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET)) {
|
||||||
return Err(
|
return Err(
|
||||||
"Dependency {} is not available on {}",
|
"Dependency {} is not available on {}",
|
||||||
dep.id, GEODE_PLATFORM_NAME
|
dep.id, GEODE_PLATFORM_NAME
|
||||||
|
@ -606,7 +517,7 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
||||||
"reason is that the version of the dependency this mod "
|
"reason is that the version of the dependency this mod "
|
||||||
"depends on is not available. Please let the the developer "
|
"depends on is not available. Please let the the developer "
|
||||||
"({}) of the mod know!",
|
"({}) of the mod know!",
|
||||||
dep.id, dep.version.toString(), item->info.developer()
|
dep.id, dep.version.toString(), item->getModInfo().developer()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -618,10 +529,10 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
||||||
return Ok(list);
|
return Ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Index::installNext(size_t index, IndexInstallList const& list) {
|
void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
|
||||||
auto postError = [this, list](std::string const& error) {
|
auto postError = [this, list](std::string const& error) {
|
||||||
m_runningInstallations.erase(list.target);
|
m_runningInstallations.erase(list.target);
|
||||||
ModInstallEvent(list.target->info.id(), error).post();
|
ModInstallEvent(list.target->getModInfo().id(), error).post();
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we're at the end of the list, move the downloaded items to mods
|
// If we're at the end of the list, move the downloaded items to mods
|
||||||
|
@ -630,12 +541,12 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
|
||||||
// Move all downloaded files
|
// Move all downloaded files
|
||||||
for (auto& item : list.list) {
|
for (auto& item : list.list) {
|
||||||
// If the mod is already installed, delete the old .geode file
|
// If the mod is already installed, delete the old .geode file
|
||||||
if (auto mod = Loader::get()->getInstalledMod(item->info.id())) {
|
if (auto mod = Loader::get()->getInstalledMod(item->getModInfo().id())) {
|
||||||
auto res = mod->uninstall();
|
auto res = mod->uninstall();
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return postError(fmt::format(
|
return postError(fmt::format(
|
||||||
"Unable to uninstall old version of {}: {}",
|
"Unable to uninstall old version of {}: {}",
|
||||||
item->info.id(), res.unwrapErr()
|
item->getModInfo().id(), res.unwrapErr()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -643,13 +554,13 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
|
||||||
// Move the temp file
|
// Move the temp file
|
||||||
try {
|
try {
|
||||||
ghc::filesystem::rename(
|
ghc::filesystem::rename(
|
||||||
dirs::getTempDir() / (item->info.id() + ".index"),
|
dirs::getTempDir() / (item->getModInfo().id() + ".index"),
|
||||||
dirs::getModsDir() / (item->info.id() + ".geode")
|
dirs::getModsDir() / (item->getModInfo().id() + ".geode")
|
||||||
);
|
);
|
||||||
} catch(std::exception& e) {
|
} catch(std::exception& e) {
|
||||||
return postError(fmt::format(
|
return postError(fmt::format(
|
||||||
"Unable to install {}: {}",
|
"Unable to install {}: {}",
|
||||||
item->info.id(), e.what()
|
item->getModInfo().id(), e.what()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -657,7 +568,7 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
|
||||||
// load mods
|
// load mods
|
||||||
Loader::get()->refreshModsList();
|
Loader::get()->refreshModsList();
|
||||||
|
|
||||||
ModInstallEvent(list.target->info.id(), UpdateFinished()).post();
|
ModInstallEvent(list.target->getModInfo().id(), UpdateFinished()).post();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,10 +579,10 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
|
||||||
};
|
};
|
||||||
|
|
||||||
auto item = list.list.at(index);
|
auto item = list.list.at(index);
|
||||||
auto tempFile = dirs::getTempDir() / (item->info.id() + ".index");
|
auto tempFile = dirs::getTempDir() / (item->getModInfo().id() + ".index");
|
||||||
m_runningInstallations[list.target] = web::AsyncWebRequest()
|
m_runningInstallations[list.target] = web::AsyncWebRequest()
|
||||||
.join("install_item_" + item->info.id())
|
.join("install_item_" + item->getModInfo().id())
|
||||||
.fetch(item->download.url)
|
.fetch(item->getDownloadURL())
|
||||||
.into(tempFile)
|
.into(tempFile)
|
||||||
.then([=](auto) {
|
.then([=](auto) {
|
||||||
// Check for 404
|
// Check for 404
|
||||||
|
@ -680,25 +591,25 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
|
||||||
return postError(fmt::format(
|
return postError(fmt::format(
|
||||||
"Binary file download for {} returned \"404 Not found\". "
|
"Binary file download for {} returned \"404 Not found\". "
|
||||||
"Report this to the Geode development team.",
|
"Report this to the Geode development team.",
|
||||||
item->info.id()
|
item->getModInfo().id()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify checksum
|
// Verify checksum
|
||||||
ModInstallEvent(
|
ModInstallEvent(
|
||||||
list.target->info.id(),
|
list.target->getModInfo().id(),
|
||||||
UpdateProgress(
|
UpdateProgress(
|
||||||
scaledProgress(100),
|
scaledProgress(100),
|
||||||
fmt::format("Verifying {}", item->info.id())
|
fmt::format("Verifying {}", item->getModInfo().id())
|
||||||
)
|
)
|
||||||
).post();
|
).post();
|
||||||
|
|
||||||
if (::calculateHash(tempFile) != item->download.hash) {
|
if (::calculateHash(tempFile) != item->getPackageHash()) {
|
||||||
return postError(fmt::format(
|
return postError(fmt::format(
|
||||||
"Checksum mismatch with {}! (Downloaded file did not match what "
|
"Checksum mismatch with {}! (Downloaded file did not match what "
|
||||||
"was expected. Try again, and if the download fails another time, "
|
"was expected. Try again, and if the download fails another time, "
|
||||||
"report this to the Geode development team.)",
|
"report this to the Geode development team.)",
|
||||||
item->info.id()
|
item->getModInfo().id()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -708,15 +619,15 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
|
||||||
.expect([postError, list, item](std::string const& err) {
|
.expect([postError, list, item](std::string const& err) {
|
||||||
postError(fmt::format(
|
postError(fmt::format(
|
||||||
"Unable to download {}: {}",
|
"Unable to download {}: {}",
|
||||||
item->info.id(), err
|
item->getModInfo().id(), err
|
||||||
));
|
));
|
||||||
})
|
})
|
||||||
.progress([this, item, list, scaledProgress](auto&, double now, double total) {
|
.progress([this, item, list, scaledProgress](auto&, double now, double total) {
|
||||||
ModInstallEvent(
|
ModInstallEvent(
|
||||||
list.target->info.id(),
|
list.target->getModInfo().id(),
|
||||||
UpdateProgress(
|
UpdateProgress(
|
||||||
scaledProgress(now / total * 100.0),
|
scaledProgress(now / total * 100.0),
|
||||||
fmt::format("Downloading {}", item->info.id())
|
fmt::format("Downloading {}", item->getModInfo().id())
|
||||||
)
|
)
|
||||||
).post();
|
).post();
|
||||||
})
|
})
|
||||||
|
@ -728,22 +639,22 @@ void Index::installNext(size_t index, IndexInstallList const& list) {
|
||||||
|
|
||||||
void Index::cancelInstall(IndexItemHandle item) {
|
void Index::cancelInstall(IndexItemHandle item) {
|
||||||
Loader::get()->queueInGDThread([this, item]() {
|
Loader::get()->queueInGDThread([this, item]() {
|
||||||
if (m_runningInstallations.count(item)) {
|
if (m_impl->m_runningInstallations.count(item)) {
|
||||||
m_runningInstallations.at(item)->cancel();
|
m_impl->m_runningInstallations.at(item)->cancel();
|
||||||
m_runningInstallations.erase(item);
|
m_impl->m_runningInstallations.erase(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Index::install(IndexInstallList const& list) {
|
void Index::install(IndexInstallList const& list) {
|
||||||
Loader::get()->queueInGDThread([this, list]() {
|
Loader::get()->queueInGDThread([this, list]() {
|
||||||
this->installNext(0, list);
|
m_impl->installNext(0, list);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Index::install(IndexItemHandle item) {
|
void Index::install(IndexItemHandle item) {
|
||||||
Loader::get()->queueInGDThread([this, item]() {
|
Loader::get()->queueInGDThread([this, item]() {
|
||||||
if (m_runningInstallations.count(item)) {
|
if (m_impl->m_runningInstallations.count(item)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto list = this->getInstallList(item);
|
auto list = this->getInstallList(item);
|
||||||
|
@ -751,7 +662,7 @@ void Index::install(IndexItemHandle item) {
|
||||||
this->install(list.unwrap());
|
this->install(list.unwrap());
|
||||||
} else {
|
} else {
|
||||||
ModInstallEvent(
|
ModInstallEvent(
|
||||||
item->info.id(),
|
item->getModInfo().id(),
|
||||||
UpdateFailed(list.unwrapErr())
|
UpdateFailed(list.unwrapErr())
|
||||||
).post();
|
).post();
|
||||||
}
|
}
|
||||||
|
@ -762,9 +673,9 @@ void Index::install(IndexItemHandle item) {
|
||||||
|
|
||||||
std::unordered_set<std::string> Index::getTags() const {
|
std::unordered_set<std::string> Index::getTags() const {
|
||||||
std::unordered_set<std::string> tags;
|
std::unordered_set<std::string> tags;
|
||||||
for (auto& [_, versions] : m_items) {
|
for (auto& [_, versions] : m_impl->m_items) {
|
||||||
for (auto& [_, item] : versions) {
|
for (auto& [_, item] : versions) {
|
||||||
for (auto& tag : item->tags) {
|
for (auto& tag : item->getTags()) {
|
||||||
tags.insert(tag);
|
tags.insert(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ CCNode* geode::createModLogo(Mod* mod, CCSize const& size) {
|
||||||
|
|
||||||
CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) {
|
CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) {
|
||||||
CCNode* spr = nullptr;
|
CCNode* spr = nullptr;
|
||||||
auto logoPath = ghc::filesystem::absolute(item->path / "logo.png");
|
auto logoPath = ghc::filesystem::absolute(item->getPath() / "logo.png");
|
||||||
spr = CCSprite::create(logoPath.string().c_str());
|
spr = CCSprite::create(logoPath.string().c_str());
|
||||||
if (!spr) {
|
if (!spr) {
|
||||||
spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
||||||
|
@ -96,7 +96,7 @@ CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) {
|
||||||
if (!spr) {
|
if (!spr) {
|
||||||
spr = CCLabelBMFont::create("N/A", "goldFont.fnt");
|
spr = CCLabelBMFont::create("N/A", "goldFont.fnt");
|
||||||
}
|
}
|
||||||
if (item->isFeatured) {
|
if (item->isFeatured()) {
|
||||||
auto glowSize = size + CCSize(4.f, 4.f);
|
auto glowSize = size + CCSize(4.f, 4.f);
|
||||||
|
|
||||||
auto logoGlow = CCSprite::createWithSpriteFrameName("logo-glow.png"_spr);
|
auto logoGlow = CCSprite::createWithSpriteFrameName("logo-glow.png"_spr);
|
||||||
|
|
|
@ -27,7 +27,7 @@ bool DevProfilePopup::setup(std::string const& developer) {
|
||||||
|
|
||||||
// index mods
|
// index mods
|
||||||
for (auto& item : Index::get()->getItemsByDeveloper(developer)) {
|
for (auto& item : Index::get()->getItemsByDeveloper(developer)) {
|
||||||
if (Loader::get()->isModInstalled(item->info.id())) {
|
if (Loader::get()->isModInstalled(item->getModInfo().id())) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto cell = IndexItemCell::create(
|
auto cell = IndexItemCell::create(
|
||||||
|
|
|
@ -290,7 +290,7 @@ LocalModInfoPopup::LocalModInfoPopup()
|
||||||
bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
|
bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
|
||||||
m_item = Index::get()->getMajorItem(mod->getModInfo().id());
|
m_item = Index::get()->getMajorItem(mod->getModInfo().id());
|
||||||
if (m_item)
|
if (m_item)
|
||||||
m_installListener.setFilter(m_item->info.id());
|
m_installListener.setFilter(m_item->getModInfo().id());
|
||||||
m_mod = mod;
|
m_mod = mod;
|
||||||
|
|
||||||
if (!ModInfoPopup::init(mod->getModInfo(), list)) return false;
|
if (!ModInfoPopup::init(mod->getModInfo(), list)) return false;
|
||||||
|
@ -382,10 +382,10 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
|
||||||
|
|
||||||
// TODO: use column layout here?
|
// TODO: use column layout here?
|
||||||
|
|
||||||
if (m_item->info.version().getMajor() > minorIndexItem->info.version().getMajor()) {
|
if (m_item->getModInfo().version().getMajor() > minorIndexItem->getModInfo().version().getMajor()) {
|
||||||
// has major update
|
// has major update
|
||||||
m_latestVersionLabel = CCLabelBMFont::create(
|
m_latestVersionLabel = CCLabelBMFont::create(
|
||||||
("Available: " + m_item->info.version().toString()).c_str(),
|
("Available: " + m_item->getModInfo().version().toString()).c_str(),
|
||||||
"bigFont.fnt"
|
"bigFont.fnt"
|
||||||
);
|
);
|
||||||
m_latestVersionLabel->setScale(.35f);
|
m_latestVersionLabel->setScale(.35f);
|
||||||
|
@ -395,10 +395,10 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
|
||||||
m_mainLayer->addChild(m_latestVersionLabel);
|
m_mainLayer->addChild(m_latestVersionLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minorIndexItem->info.version() > mod->getModInfo().version()) {
|
if (minorIndexItem->getModInfo().version() > mod->getModInfo().version()) {
|
||||||
// has minor update
|
// has minor update
|
||||||
m_minorVersionLabel = CCLabelBMFont::create(
|
m_minorVersionLabel = CCLabelBMFont::create(
|
||||||
("Available: " + minorIndexItem->info.version().toString()).c_str(),
|
("Available: " + minorIndexItem->getModInfo().version().toString()).c_str(),
|
||||||
"bigFont.fnt"
|
"bigFont.fnt"
|
||||||
);
|
);
|
||||||
m_minorVersionLabel->setScale(.35f);
|
m_minorVersionLabel->setScale(.35f);
|
||||||
|
@ -516,7 +516,8 @@ void LocalModInfoPopup::onUpdate(CCObject*) {
|
||||||
[](IndexItemHandle handle) {
|
[](IndexItemHandle handle) {
|
||||||
return fmt::format(
|
return fmt::format(
|
||||||
" - <cr>{}</c> (<cy>{}</c>)",
|
" - <cr>{}</c> (<cy>{}</c>)",
|
||||||
handle->info.name(), handle->info.id()
|
handle->getModInfo().name(),
|
||||||
|
handle->getModInfo().id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -683,11 +684,11 @@ IndexItemInfoPopup::IndexItemInfoPopup()
|
||||||
|
|
||||||
bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
|
bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
|
||||||
m_item = item;
|
m_item = item;
|
||||||
m_installListener.setFilter(m_item->info.id());
|
m_installListener.setFilter(m_item->getModInfo().id());
|
||||||
|
|
||||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||||
|
|
||||||
if (!ModInfoPopup::init(item->info, list)) return false;
|
if (!ModInfoPopup::init(item->getModInfo(), list)) return false;
|
||||||
|
|
||||||
m_installBtnSpr = IconButtonSprite::create(
|
m_installBtnSpr = IconButtonSprite::create(
|
||||||
"GE_button_01.png"_spr,
|
"GE_button_01.png"_spr,
|
||||||
|
@ -770,7 +771,8 @@ void IndexItemInfoPopup::onInstall(CCObject*) {
|
||||||
[](IndexItemHandle handle) {
|
[](IndexItemHandle handle) {
|
||||||
return fmt::format(
|
return fmt::format(
|
||||||
" - <cr>{}</c> (<cy>{}</c>)",
|
" - <cr>{}</c> (<cy>{}</c>)",
|
||||||
handle->info.name(), handle->info.id()
|
handle->getModInfo().name(),
|
||||||
|
handle->getModInfo().id()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -811,7 +813,7 @@ CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ModInfo IndexItemInfoPopup::getModInfo() const {
|
ModInfo IndexItemInfoPopup::getModInfo() const {
|
||||||
return m_item->info;
|
return m_item->getModInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
IndexItemInfoPopup* IndexItemInfoPopup::create(
|
IndexItemInfoPopup* IndexItemInfoPopup::create(
|
||||||
|
|
|
@ -273,7 +273,7 @@ bool ModCell::init(
|
||||||
ComparableVersionInfo(mod->getModInfo().version(), VersionCompare::MoreEq)
|
ComparableVersionInfo(mod->getModInfo().version(), VersionCompare::MoreEq)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (latestIndexItem->info.version().getMajor() > minorIndexItem->info.version().getMajor()) {
|
if (latestIndexItem->getModInfo().version().getMajor() > minorIndexItem->getModInfo().version().getMajor()) {
|
||||||
auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
|
auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
|
||||||
updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
|
updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
|
||||||
updateIcon->setZOrder(99);
|
updateIcon->setZOrder(99);
|
||||||
|
@ -327,7 +327,7 @@ bool IndexItemCell::init(
|
||||||
|
|
||||||
m_item = item;
|
m_item = item;
|
||||||
|
|
||||||
this->setupInfo(item->info, item->tags.size(), display);
|
this->setupInfo(item->getModInfo(), item->getTags().size(), display);
|
||||||
|
|
||||||
auto viewSpr = ButtonSprite::create(
|
auto viewSpr = ButtonSprite::create(
|
||||||
"View", "bigFont.fnt", "GJ_button_01.png", .8f
|
"View", "bigFont.fnt", "GJ_button_01.png", .8f
|
||||||
|
@ -337,9 +337,9 @@ bool IndexItemCell::init(
|
||||||
auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo));
|
auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo));
|
||||||
m_menu->addChild(viewBtn);
|
m_menu->addChild(viewBtn);
|
||||||
|
|
||||||
if (item->tags.size()) {
|
if (item->getTags().size()) {
|
||||||
float x = m_height / 2 + this->getLogoSize() / 2 + 13.f;
|
float x = m_height / 2 + this->getLogoSize() / 2 + 13.f;
|
||||||
for (auto& category : item->tags) {
|
for (auto& category : item->getTags()) {
|
||||||
auto node = TagNode::create(category);
|
auto node = TagNode::create(category);
|
||||||
node->setAnchorPoint({ .0f, .5f });
|
node->setAnchorPoint({ .0f, .5f });
|
||||||
node->setPositionX(x);
|
node->setPositionX(x);
|
||||||
|
@ -364,7 +364,7 @@ bool IndexItemCell::init(
|
||||||
void IndexItemCell::updateState() {}
|
void IndexItemCell::updateState() {}
|
||||||
|
|
||||||
std::string IndexItemCell::getDeveloper() const {
|
std::string IndexItemCell::getDeveloper() const {
|
||||||
return m_item->info.developer();
|
return m_item->getModInfo().developer();
|
||||||
}
|
}
|
||||||
|
|
||||||
CCNode* IndexItemCell::createLogo(CCSize const& size) {
|
CCNode* IndexItemCell::createLogo(CCSize const& size) {
|
||||||
|
|
|
@ -95,33 +95,33 @@ static std::optional<int> queryMatch(ModListQuery const& query, Mod* mod) {
|
||||||
static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle item) {
|
static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle item) {
|
||||||
// if no force visibility was provided and item is already installed, don't
|
// if no force visibility was provided and item is already installed, don't
|
||||||
// show it
|
// show it
|
||||||
if (!query.forceVisibility && Loader::get()->isModInstalled(item->info.id())) {
|
if (!query.forceVisibility && Loader::get()->isModInstalled(item->getModInfo().id())) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
// make sure all tags match
|
// make sure all tags match
|
||||||
for (auto& tag : query.tags) {
|
for (auto& tag : query.tags) {
|
||||||
if (!item->tags.count(tag)) {
|
if (!item->getTags().count(tag)) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// make sure at least some platform matches
|
// make sure at least some platform matches
|
||||||
if (!ranges::contains(query.platforms, [item](PlatformID id) {
|
if (!ranges::contains(query.platforms, [item](PlatformID id) {
|
||||||
return item->download.platforms.count(id);
|
return item->getAvailablePlatforms().count(id);
|
||||||
})) {
|
})) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
// otherwise match keywords
|
// otherwise match keywords
|
||||||
if (auto match = queryMatchKeywords(query, item->info)) {
|
if (auto match = queryMatchKeywords(query, item->getModInfo())) {
|
||||||
auto weighted = match.value();
|
auto weighted = match.value();
|
||||||
// add extra weight on tag matches
|
// add extra weight on tag matches
|
||||||
if (query.keywords) {
|
if (query.keywords) {
|
||||||
WEIGHTED_MATCH_ADD(ranges::join(item->tags, " "), 1.4);
|
WEIGHTED_MATCH_ADD(ranges::join(item->getTags(), " "), 1.4);
|
||||||
}
|
}
|
||||||
// add extra weight to featured items to keep power consolidated in the
|
// add extra weight to featured items to keep power consolidated in the
|
||||||
// hands of the rich Geode bourgeoisie
|
// hands of the rich Geode bourgeoisie
|
||||||
// the number 420 is a reference to the number one bourgeois of modern
|
// the number 420 is a reference to the number one bourgeois of modern
|
||||||
// society, elon musk
|
// society, elon musk
|
||||||
weighted += item->isFeatured ? 420 : 0;
|
weighted += item->isFeatured() ? 420 : 0;
|
||||||
return static_cast<int>(weighted);
|
return static_cast<int>(weighted);
|
||||||
}
|
}
|
||||||
// keywords must match bruh
|
// keywords must match bruh
|
||||||
|
|
Loading…
Reference in a new issue