aboutsummaryrefslogtreecommitdiff
path: root/launcher/minecraft/mod
diff options
context:
space:
mode:
Diffstat (limited to 'launcher/minecraft/mod')
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp424
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h93
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp336
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.h274
-rw-r--r--launcher/minecraft/mod/tasks/BasicFolderLoadTask.h44
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.cpp16
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.h15
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp8
-rw-r--r--launcher/minecraft/mod/tasks/ModFolderLoadTask.h12
9 files changed, 771 insertions, 451 deletions
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index d4c5e819..597f9807 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -49,226 +49,91 @@
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
-ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : QAbstractListModel(), m_dir(dir), m_is_indexed(is_indexed)
+ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(dir), m_is_indexed(is_indexed)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
- m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
- m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
- m_watcher = new QFileSystemWatcher(this);
- connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
}
-void ModFolderModel::startWatching()
+Task* ModFolderModel::createUpdateTask()
{
- if(is_watching)
- return;
+ auto index_dir = indexDir();
+ auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
+ m_first_folder_load = false;
+ return task;
+}
+void ModFolderModel::startWatching()
+{
// Remove orphaned metadata next time
m_first_folder_load = true;
-
- update();
-
- // Watch the mods folder
- is_watching = m_watcher->addPath(m_dir.absolutePath());
- if (is_watching) {
- qDebug() << "Started watching " << m_dir.absolutePath();
- } else {
- qDebug() << "Failed to start watching " << m_dir.absolutePath();
- }
-
- // Watch the mods index folder
- is_watching = m_watcher->addPath(indexDir().absolutePath());
- if (is_watching) {
- qDebug() << "Started watching " << indexDir().absolutePath();
- } else {
- qDebug() << "Failed to start watching " << indexDir().absolutePath();
- }
+ ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
}
void ModFolderModel::stopWatching()
{
- if(!is_watching)
- return;
-
- is_watching = !m_watcher->removePath(m_dir.absolutePath());
- if (!is_watching) {
- qDebug() << "Stopped watching " << m_dir.absolutePath();
- } else {
- qDebug() << "Failed to stop watching " << m_dir.absolutePath();
- }
-
- is_watching = !m_watcher->removePath(indexDir().absolutePath());
- if (!is_watching) {
- qDebug() << "Stopped watching " << indexDir().absolutePath();
- } else {
- qDebug() << "Failed to stop watching " << indexDir().absolutePath();
- }
+ ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
}
-bool ModFolderModel::update()
+void ModFolderModel::onUpdateSucceeded()
{
- if (!isValid()) {
- return false;
- }
- if(m_update) {
- scheduled_update = true;
- return true;
- }
-
- auto index_dir = indexDir();
- auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
- m_first_folder_load = false;
-
- m_update = task->result();
+ auto update_results = static_cast<ModFolderLoadTask*>(m_current_update_task.get())->result();
- QThreadPool *threadPool = QThreadPool::globalInstance();
- connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
-
- threadPool->start(task);
- return true;
-}
+ auto& new_mods = update_results->mods;
-void ModFolderModel::finishUpdate()
-{
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
- auto currentList = modsIndex.keys();
- QSet<QString> currentSet(currentList.begin(), currentList.end());
- auto & newMods = m_update->mods;
- auto newList = newMods.keys();
- QSet<QString> newSet(newList.begin(), newList.end());
+ auto current_list = m_resources_index.keys();
+ QSet<QString> current_set(current_list.begin(), current_list.end());
+
+ auto new_list = new_mods.keys();
+ QSet<QString> new_set(new_list.begin(), new_list.end());
#else
- QSet<QString> currentSet = modsIndex.keys().toSet();
- auto& newMods = m_update->mods;
- QSet<QString> newSet = newMods.keys().toSet();
+ QSet<QString> current_set(m_resources_index.keys().toSet());
+ QSet<QString> new_set(new_mods.keys().toSet());
#endif
- // see if the kept mods changed in some way
- {
- QSet<QString> kept = currentSet;
- kept.intersect(newSet);
- for(auto& keptMod : kept) {
- auto newMod = newMods[keptMod];
- auto row = modsIndex[keptMod];
- auto currentMod = mods[row];
- if(newMod->dateTimeChanged() == currentMod->dateTimeChanged()) {
- // no significant change, ignore...
- continue;
- }
- auto oldMod = mods[row];
- if(oldMod->isResolving()) {
- activeTickets.remove(oldMod->resolutionTicket());
- }
-
- mods[row] = newMod;
- resolveMod(mods[row]);
- emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
- }
- }
-
- // remove mods no longer present
- {
- QSet<QString> removed = currentSet;
- QList<int> removedRows;
- removed.subtract(newSet);
- for(auto & removedMod: removed) {
- removedRows.append(modsIndex[removedMod]);
- }
- std::sort(removedRows.begin(), removedRows.end(), std::greater<int>());
- for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) {
- int removedIndex = *iter;
- beginRemoveRows(QModelIndex(), removedIndex, removedIndex);
- auto removedIter = mods.begin() + removedIndex;
- if((*removedIter)->isResolving()) {
- activeTickets.remove((*removedIter)->resolutionTicket());
- }
-
- mods.erase(removedIter);
- endRemoveRows();
- }
- }
-
- // add new mods to the end
- {
- QSet<QString> added = newSet;
- added.subtract(currentSet);
-
- // When you have a Qt build with assertions turned on, proceeding here will abort the application
- if (added.size() > 0) {
- beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1);
- for (auto& addedMod : added) {
- mods.append(newMods[addedMod]);
- resolveMod(mods.last());
- }
- endInsertRows();
- }
- }
-
- // update index
- {
- modsIndex.clear();
- int idx = 0;
- for(auto mod: mods) {
- modsIndex[mod->internal_id()] = idx;
- idx++;
- }
- }
-
- m_update.reset();
+ applyUpdates(current_set, new_set, new_mods);
+
+ update_results.reset();
+ m_current_update_task.reset();
emit updateFinished();
- if(scheduled_update) {
- scheduled_update = false;
+ if(m_scheduled_update) {
+ m_scheduled_update = false;
update();
}
}
-void ModFolderModel::resolveMod(Mod::Ptr m)
+Task* ModFolderModel::createParseTask(Resource const& resource)
{
- if(!m->shouldResolve()) {
- return;
- }
-
- auto task = new LocalModParseTask(nextResolutionTicket, m->type(), m->fileinfo());
- auto result = task->result();
- result->id = m->internal_id();
- activeTickets.insert(nextResolutionTicket, result);
- m->setResolving(true, nextResolutionTicket);
- nextResolutionTicket++;
- QThreadPool *threadPool = QThreadPool::globalInstance();
- connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse);
- threadPool->start(task);
+ return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
}
-void ModFolderModel::finishModParse(int token)
+void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
{
- auto iter = activeTickets.find(token);
- if(iter == activeTickets.end()) {
+ auto iter = m_active_parse_tasks.constFind(ticket);
+ if (iter == m_active_parse_tasks.constEnd())
return;
- }
- auto result = *iter;
- activeTickets.remove(token);
- int row = modsIndex[result->id];
- auto mod = mods[row];
- mod->finishResolvingWithDetails(result->details);
+
+ int row = m_resources_index[mod_id];
+
+ auto parse_task = *iter;
+ auto cast_task = static_cast<LocalModParseTask*>(parse_task.get());
+
+ Q_ASSERT(cast_task->token() == ticket);
+
+ auto resource = find(mod_id);
+
+ auto result = cast_task->result();
+ if (result && resource)
+ resource->finishResolvingWithDetails(result->details);
+
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
-}
-void ModFolderModel::disableInteraction(bool disabled)
-{
- if (interaction_disabled == disabled) {
- return;
- }
- interaction_disabled = disabled;
- if(size()) {
- emit dataChanged(index(0), index(size() - 1));
- }
+ parse_task->deleteLater();
+ m_active_parse_tasks.remove(ticket);
}
-void ModFolderModel::directoryChanged(QString path)
-{
- update();
-}
bool ModFolderModel::isValid()
{
@@ -277,104 +142,28 @@ bool ModFolderModel::isValid()
auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList<Mod::Ptr>
{
- QList<Mod::Ptr> selected_mods;
+ QList<Mod::Ptr> selected_resources;
for (auto i : indexes) {
if(i.column() != 0)
continue;
- selected_mods.push_back(mods[i.row()]);
+ selected_resources.push_back(at(i.row()));
}
- return selected_mods;
+ return selected_resources;
}
-// FIXME: this does not take disabled mod (with extra .disable extension) into account...
-bool ModFolderModel::installMod(const QString &filename)
+auto ModFolderModel::allMods() -> QList<Mod::Ptr>
{
- if(interaction_disabled) {
- return false;
- }
-
- // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
- auto originalPath = FS::NormalizePath(filename);
- QFileInfo fileinfo(originalPath);
-
- if (!fileinfo.exists() || !fileinfo.isReadable())
- {
- qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath;
- return false;
- }
- qDebug() << "installing: " << fileinfo.absoluteFilePath();
-
- Mod installedMod(fileinfo);
- if (!installedMod.valid())
- {
- qDebug() << originalPath << "is not a valid mod. Ignoring it.";
- return false;
- }
-
- auto type = installedMod.type();
- if (type == Mod::MOD_UNKNOWN)
- {
- qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it.";
- return false;
- }
-
- auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName()));
- if(originalPath == newpath)
- {
- qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense...";
- return false;
- }
+ QList<Mod::Ptr> mods;
- if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
- {
- if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled")))
- {
- if(!QFile::remove(newpath))
- {
- // FIXME: report error in a user-visible way
- qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed.";
- return false;
- }
- qDebug() << newpath << "has been deleted.";
- }
- if (!QFile::copy(fileinfo.filePath(), newpath))
- {
- qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed.";
- // FIXME: report error in a user-visible way
- return false;
- }
- FS::updateTimestamp(newpath);
- QFileInfo newpathInfo(newpath);
- installedMod.repath(newpathInfo);
- update();
- return true;
- }
- else if (type == Mod::MOD_FOLDER)
- {
- QString from = fileinfo.filePath();
- if(QFile::exists(newpath))
- {
- qDebug() << "Ignoring folder " << from << ", it would merge with " << newpath;
- return false;
- }
+ for (auto res : m_resources)
+ mods.append(static_cast<Mod*>(res.get()));
- if (!FS::copy(from, newpath)())
- {
- qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed.";
- return false;
- }
- QFileInfo newpathInfo(newpath);
- installedMod.repath(newpathInfo);
- update();
- return true;
- }
- return false;
+ return mods;
}
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
{
-
for(auto mod : allMods()){
if(mod->fileinfo().fileName() == filename){
auto index_dir = indexDir();
@@ -388,7 +177,7 @@ bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadat
bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable)
{
- if(interaction_disabled) {
+ if(!m_can_interact) {
return false;
}
@@ -407,7 +196,7 @@ bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusActio
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
{
- if(interaction_disabled) {
+ if(!m_can_interact) {
return false;
}
@@ -419,7 +208,7 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
if(i.column() != 0) {
continue;
}
- auto m = mods[i.row()];
+ auto m = at(i.row());
auto index_dir = indexDir();
m->destroy(index_dir);
}
@@ -433,48 +222,45 @@ int ModFolderModel::columnCount(const QModelIndex &parent) const
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
{
- if (!index.isValid())
- return QVariant();
+ if (!validateIndex(index))
+ return {};
int row = index.row();
int column = index.column();
- if (row < 0 || row >= mods.size())
- return QVariant();
-
switch (role)
{
case Qt::DisplayRole:
switch (column)
{
case NameColumn:
- return mods[row]->name();
+ return m_resources[row]->name();
case VersionColumn: {
- switch(mods[row]->type()) {
- case Mod::MOD_FOLDER:
+ switch(m_resources[row]->type()) {
+ case ResourceType::FOLDER:
return tr("Folder");
- case Mod::MOD_SINGLEFILE:
+ case ResourceType::SINGLEFILE:
return tr("File");
default:
break;
}
- return mods[row]->version();
+ return at(row)->version();
}
case DateColumn:
- return mods[row]->dateTimeChanged();
+ return m_resources[row]->dateTimeChanged();
default:
return QVariant();
}
case Qt::ToolTipRole:
- return mods[row]->internal_id();
+ return m_resources[row]->internal_id();
case Qt::CheckStateRole:
switch (column)
{
case ActiveColumn:
- return mods[row]->enabled() ? Qt::Checked : Qt::Unchecked;
+ return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
default:
return QVariant();
}
@@ -499,11 +285,11 @@ bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, in
bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action)
{
- if(row < 0 || row >= mods.size()) {
+ if(row < 0 || row >= m_resources.size()) {
return false;
}
- auto &mod = mods[row];
+ auto mod = at(row);
bool desiredStatus;
switch(action) {
case Enable:
@@ -528,12 +314,12 @@ bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction actio
return false;
}
auto newId = mod->internal_id();
- if(modsIndex.contains(newId)) {
+ if(m_resources_index.contains(newId)) {
// NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled
// But is it necessary?
}
- modsIndex.remove(oldId);
- modsIndex[newId] = row;
+ m_resources_index.remove(oldId);
+ m_resources_index[newId] = row;
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
return true;
}
@@ -577,65 +363,3 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
return QVariant();
}
-Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const
-{
- Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
- auto flags = defaultFlags;
- if(interaction_disabled) {
- flags &= ~Qt::ItemIsDropEnabled;
- }
- else
- {
- flags |= Qt::ItemIsDropEnabled;
- if(index.isValid()) {
- flags |= Qt::ItemIsUserCheckable;
- }
- }
- return flags;
-}
-
-Qt::DropActions ModFolderModel::supportedDropActions() const
-{
- // copy from outside, move from within and other mod lists
- return Qt::CopyAction | Qt::MoveAction;
-}
-
-QStringList ModFolderModel::mimeTypes() const
-{
- QStringList types;
- types << "text/uri-list";
- return types;
-}
-
-bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
-{
- if (action == Qt::IgnoreAction)
- {
- return true;
- }
-
- // check if the action is supported
- if (!data || !(action & supportedDropActions()))
- {
- return false;
- }
-
- // files dropped from outside?
- if (data->hasUrls())
- {
- auto urls = data->urls();
- for (auto url : urls)
- {
- // only local files may be dropped...
- if (!url.isLocalFile())
- {
- continue;
- }
- // TODO: implement not only copy, but also move
- // FIXME: handle errors here
- installMod(url.toLocalFile());
- }
- return true;
- }
- return false;
-}
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index 3d6efac3..a90457d5 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -44,6 +44,7 @@
#include <QAbstractListModel>
#include "Mod.h"
+#include "ResourceFolderModel.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
@@ -56,7 +57,7 @@ class QFileSystemWatcher;
* A legacy mod list.
* Backed by a folder.
*/
-class ModFolderModel : public QAbstractListModel
+class ModFolderModel : public ResourceFolderModel
{
Q_OBJECT
public:
@@ -75,48 +76,18 @@ public:
};
ModFolderModel(const QString &dir, bool is_indexed = false);
- virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
- Qt::DropActions supportedDropActions() const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
- /// flags, mostly to support drag&drop
- virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
- QStringList mimeTypes() const override;
- bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+ int columnCount(const QModelIndex &parent) const override;
- virtual int rowCount(const QModelIndex &) const override
- {
- return size();
- }
+ [[nodiscard]] Task* createUpdateTask() override;
+ [[nodiscard]] Task* createParseTask(Resource const&) override;
- virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
- virtual int columnCount(const QModelIndex &parent) const override;
-
- size_t size() const
- {
- return mods.size();
- }
- ;
- bool empty() const
- {
- return size() == 0;
- }
- Mod& operator[](size_t index)
- {
- return *mods[index];
- }
- const Mod& at(size_t index) const
- {
- return *mods.at(index);
- }
-
- /// Reloads the mod list and returns true if the list changed.
- bool update();
-
- /**
- * Adds the given mod to the list at the given index - if the list supports custom ordering
- */
- bool installMod(const QString& filename);
+ // Alias for old code, consider those deprecated and don't use in new code :gun:
+ bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
+ void disableInteraction(bool disabled) { ResourceFolderModel::enableInteraction(!disabled); }
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
@@ -126,55 +97,27 @@ public:
/// Enable or disable listed mods
bool setModStatus(const QModelIndexList &indexes, ModStatusAction action);
- void startWatching();
- void stopWatching();
-
bool isValid();
- QDir& dir()
- {
- return m_dir;
- }
-
- QDir indexDir()
- {
- return { QString("%1/.index").arg(dir().absolutePath()) };
- }
+ void startWatching();
+ void stopWatching();
- const QList<Mod::Ptr>& allMods()
- {
- return mods;
- }
+ QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
auto selectedMods(QModelIndexList& indexes) -> QList<Mod::Ptr>;
+ auto allMods() -> QList<Mod::Ptr>;
-public slots:
- void disableInteraction(bool disabled);
+ RESOURCE_HELPERS(Mod)
private
slots:
- void directoryChanged(QString path);
- void finishUpdate();
- void finishModParse(int token);
-
-signals:
- void updateFinished();
+ void onUpdateSucceeded() override;
+ void onParseSucceeded(int ticket, QString resource_id) override;
private:
- void resolveMod(Mod::Ptr m);
bool setModStatus(int index, ModStatusAction action);
protected:
- QFileSystemWatcher *m_watcher;
- bool is_watching = false;
- ModFolderLoadTask::ResultPtr m_update;
- bool scheduled_update = false;
- bool interaction_disabled = false;
- QDir m_dir;
bool m_is_indexed;
bool m_first_folder_load = true;
- QMap<QString, int> modsIndex;
- QMap<int, LocalModParseTask::ResultPtr> activeTickets;
- int nextResolutionTicket = 0;
- QList<Mod::Ptr> mods;
};
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
new file mode 100644
index 00000000..4867a8c2
--- /dev/null
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -0,0 +1,336 @@
+#include "ResourceFolderModel.h"
+
+#include <QDebug>
+#include <QMimeData>
+#include <QThreadPool>
+#include <QUrl>
+
+#include "FileSystem.h"
+
+#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
+
+#include "tasks/Task.h"
+
+ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractListModel(parent), m_dir(dir), m_watcher(this)
+{
+ m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
+ m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
+
+ connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
+}
+
+bool ResourceFolderModel::startWatching(const QStringList paths)
+{
+ if (m_is_watching)
+ return false;
+
+ update();
+
+ auto couldnt_be_watched = m_watcher.addPaths(paths);
+ for (auto path : paths) {
+ if (couldnt_be_watched.contains(path))
+ qDebug() << "Failed to start watching " << path;
+ else
+ qDebug() << "Started watching " << path;
+ }
+
+ m_is_watching = !m_is_watching;
+ return m_is_watching;
+}
+
+bool ResourceFolderModel::stopWatching(const QStringList paths)
+{
+ if (!m_is_watching)
+ return false;
+
+ auto couldnt_be_stopped = m_watcher.removePaths(paths);
+ for (auto path : paths) {
+ if (couldnt_be_stopped.contains(path))
+ qDebug() << "Failed to stop watching " << path;
+ else
+ qDebug() << "Stopped watching " << path;
+ }
+
+ m_is_watching = !m_is_watching;
+ return !m_is_watching;
+}
+
+bool ResourceFolderModel::installResource(QString original_path)
+{
+ if (!m_can_interact) {
+ return false;
+ }
+
+ // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
+ original_path = FS::NormalizePath(original_path);
+ QFileInfo file_info(original_path);
+
+ if (!file_info.exists() || !file_info.isReadable()) {
+ qWarning() << "Caught attempt to install non-existing file or file-like object:" << original_path;
+ return false;
+ }
+ qDebug() << "Installing: " << file_info.absoluteFilePath();
+
+ Resource resource(file_info);
+ if (!resource.valid()) {
+ qWarning() << original_path << "is not a valid resource. Ignoring it.";
+ return false;
+ }
+
+ auto new_path = FS::NormalizePath(m_dir.filePath(file_info.fileName()));
+ if (original_path == new_path) {
+ qWarning() << "Overwriting the mod (" << original_path << ") with itself makes no sense...";
+ return false;
+ }
+
+ switch (resource.type()) {
+ case ResourceType::SINGLEFILE:
+ case ResourceType::ZIPFILE:
+ case ResourceType::LITEMOD: {
+ if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) {
+ if (!QFile::remove(new_path)) {
+ qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!";
+ return false;
+ }
+ qDebug() << new_path << "has been deleted.";
+ }
+
+ if (!QFile::copy(original_path, new_path)) {
+ qCritical() << "Copy from" << original_path << "to" << new_path << "has failed.";
+ return false;
+ }
+
+ FS::updateTimestamp(new_path);
+
+ QFileInfo new_path_file_info(new_path);
+ resource.setFile(new_path_file_info);
+
+ update();
+
+ return true;
+ }
+ case ResourceType::FOLDER: {
+ if (QFile::exists(new_path)) {
+ qDebug() << "Ignoring folder '" << original_path << "', it would merge with" << new_path;
+ return false;
+ }
+
+ if (!FS::copy(original_path, new_path)()) {
+ qWarning() << "Copy of folder from" << original_path << "to" << new_path << "has (potentially partially) failed.";
+ return false;
+ }
+
+ QFileInfo newpathInfo(new_path);
+ resource.setFile(newpathInfo);
+
+ update();
+
+ return true;
+ }
+ default:
+ break;
+ }
+ return false;
+}
+
+bool ResourceFolderModel::uninstallResource(QString file_name)
+{
+ for (auto resource : m_resources) {
+ if (resource->fileinfo().fileName() == file_name)
+ return resource->destroy();
+ }
+ return false;
+}
+
+bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
+{
+ if (!m_can_interact)
+ return false;
+
+ if (indexes.isEmpty())
+ return true;
+
+ for (auto i : indexes) {
+ if (i.column() != 0) {
+ continue;
+ }
+
+ auto resource = m_resources.at(i.row());
+ resource->destroy();
+ }
+ return true;
+}
+
+bool ResourceFolderModel::update()
+{
+ // Already updating, so we schedule a future update and return.
+ if (m_current_update_task) {
+ m_scheduled_update = true;
+ return false;
+ }
+
+ m_current_update_task.reset(createUpdateTask());
+
+ connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, Qt::ConnectionType::QueuedConnection);
+ connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
+
+ auto* thread_pool = QThreadPool::globalInstance();
+ thread_pool->start(m_current_update_task.get());
+
+ return true;
+}
+
+void ResourceFolderModel::resolveResource(Resource::Ptr res)
+{
+ if (!res->shouldResolve()) {
+ return;
+ }
+
+ auto task = createParseTask(*res);
+
+ m_ticket_mutex.lock();
+ int ticket = m_next_resolution_ticket;
+ m_next_resolution_ticket += 1;
+ m_ticket_mutex.unlock();
+
+ res->setResolving(true, ticket);
+ m_active_parse_tasks.insert(ticket, task);
+
+ connect(task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
+ connect(task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection);
+
+ auto* thread_pool = QThreadPool::globalInstance();
+ thread_pool->start(task);
+}
+
+void ResourceFolderModel::onUpdateSucceeded()
+{
+ auto update_results = static_cast<BasicFolderLoadTask*>(m_current_update_task.get())->result();
+
+ auto& new_resources = update_results->resources;
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ auto current_list = m_resources_index.keys();
+ QSet<QString> current_set(current_list.begin(), current_list.end());
+
+ auto new_list = new_resources.keys();
+ QSet<QString> new_set(new_list.begin(), new_list.end());
+#else
+ QSet<QString> current_set(m_resources_index.keys().toSet());
+ QSet<QString> new_set(new_resources.keys().toSet());
+#endif
+
+ applyUpdates(current_set, new_set, new_resources);
+
+ update_results.reset();
+ m_current_update_task->deleteLater();
+ m_current_update_task.reset();
+
+ emit updateFinished();
+
+ if (m_scheduled_update) {
+ m_scheduled_update = false;
+ update();
+ }
+}
+
+void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
+{
+ auto iter = m_active_parse_tasks.constFind(ticket);
+ if (iter == m_active_parse_tasks.constEnd())
+ return;
+
+ (*iter)->deleteLater();
+ m_active_parse_tasks.remove(ticket);
+
+ int row = m_resources_index[resource_id];
+ emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
+}
+
+Task* ResourceFolderModel::createUpdateTask()
+{
+ return new BasicFolderLoadTask(m_dir);
+}
+
+void ResourceFolderModel::directoryChanged(QString path)
+{
+ update();
+}
+
+Qt::DropActions ResourceFolderModel::supportedDropActions() const
+{
+ // copy from outside, move from within and other resource lists
+ return Qt::CopyAction | Qt::MoveAction;
+}
+
+Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const
+{
+ Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
+ auto flags = defaultFlags;
+ if (!m_can_interact) {
+ flags &= ~Qt::ItemIsDropEnabled;
+ } else {
+ flags |= Qt::ItemIsDropEnabled;
+ if (index.isValid()) {
+ flags |= Qt::ItemIsUserCheckable;
+ }
+ }
+ return flags;
+}
+
+QStringList ResourceFolderModel::mimeTypes() const
+{
+ QStringList types;
+ types << "text/uri-list";
+ return types;
+}
+
+bool ResourceFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
+{
+ if (action == Qt::IgnoreAction) {
+ return true;
+ }
+
+ // check if the action is supported
+ if (!data || !(action & supportedDropActions())) {
+ return false;
+ }
+
+ // files dropped from outside?
+ if (data->hasUrls()) {
+ auto urls = data->urls();
+ for (auto url : urls) {
+ // only local files may be dropped...
+ if (!url.isLocalFile()) {
+ continue;
+ }
+ // TODO: implement not only copy, but also move
+ // FIXME: handle errors here
+ installResource(url.toLocalFile());
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ResourceFolderModel::validateIndex(const QModelIndex& index) const
+{
+ if (!index.isValid())
+ return false;
+
+ size_t row = index.row();
+ if (row < 0 || row >= size())
+ return false;
+
+ return true;
+}
+
+void ResourceFolderModel::enableInteraction(bool enabled)
+{
+ if (m_can_interact == enabled)
+ return;
+
+ m_can_interact = enabled;
+ if (size())
+ emit dataChanged(index(0), index(size() - 1));
+}
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
new file mode 100644
index 00000000..31fd7414
--- /dev/null
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -0,0 +1,274 @@
+#pragma once
+
+#include <QAbstractListModel>
+#include <QDir>
+#include <QFileSystemWatcher>
+#include <QMutex>
+#include <QSet>
+
+#include "Resource.h"
+
+#include "tasks/Task.h"
+
+class QRunnable;
+
+/** A basic model for external resources.
+ *
+ * To implement one such model, you need to implement, at the very minimum:
+ * - columnCount: The number of columns in your model.
+ * - data: How the model data is displayed and accessed.
+ * - headerData: Display properties of the header.
+ */
+class ResourceFolderModel : public QAbstractListModel {
+ Q_OBJECT
+ public:
+ ResourceFolderModel(QDir, QObject* parent = nullptr);
+
+ /** Starts watching the paths for changes.
+ *
+ * Returns whether starting to watch all the paths was successful.
+ * If one or more fails, it returns false.
+ */
+ bool startWatching(const QStringList paths);
+
+ /** Stops watching the paths for changes.
+ *
+ * Returns whether stopping to watch all the paths was successful.
+ * If one or more fails, it returns false.
+ */
+ bool stopWatching(const QStringList paths);
+
+ /** Given a path in the system, install that resource, moving it to its place in the
+ * instance file hierarchy.
+ *
+ * Returns whether the installation was succcessful.
+ */
+ virtual bool installResource(QString path);
+
+ /** Uninstall (i.e. remove all data about it) a resource, given its file name.
+ *
+ * Returns whether the removal was successful.
+ */
+ virtual bool uninstallResource(QString file_name);
+
+ virtual bool deleteResources(const QModelIndexList&);
+
+ /** Creates a new update task and start it. Returns false if no update was done, like when an update is already underway. */
+ virtual bool update();
+
+ /** Creates a new parse task, if needed, for 'res' and start it.*/
+ virtual void resolveResource(Resource::Ptr res);
+
+ [[nodiscard]] size_t size() const { return m_resources.size(); };
+ [[nodiscard]] bool empty() const { return size() == 0; }
+
+ [[nodiscard]] QDir const& dir() const { return m_dir; }
+
+ /* Qt behavior */
+
+ [[nodiscard]] int rowCount(const QModelIndex&) const override { return size(); }
+ [[nodiscard]] int columnCount(const QModelIndex&) const override = 0;
+
+ [[nodiscard]] Qt::DropActions supportedDropActions() const override;
+
+ /// flags, mostly to support drag&drop
+ [[nodiscard]] Qt::ItemFlags flags(const QModelIndex& index) const override;
+ [[nodiscard]] QStringList mimeTypes() const override;
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
+
+ [[nodiscard]] bool validateIndex(const QModelIndex& index) const;
+
+ [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override = 0;
+ bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { return false; };
+
+ [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override = 0;
+
+ public slots:
+ void enableInteraction(bool enabled);
+
+ signals:
+ void updateFinished();
+
+ protected:
+ /** This creates a new update task to be executed by update().
+ *
+ * The task should load and parse all resources necessary, and provide a way of accessing such results.
+ *
+ * This Task is normally executed when opening a page, so it shouldn't contain much heavy work.
+ * If such work is needed, try using it in the Task create by createParseTask() instead!
+ */
+ [[nodiscard]] virtual Task* createUpdateTask();
+
+ /** This creates a new parse task to be executed by onUpdateSucceeded().
+ *
+ * This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed
+ * in the background, so it slowly updates the UI as tasks get done.
+ */
+ [[nodiscard]] virtual Task* createParseTask(Resource const&) { return nullptr; };
+
+ /** Standard implementation of the model update logic.
+ *
+ * It uses set operations to find differences between the current state and the updated state,
+ * to act only on those disparities.
+ *
+ * The implementation is at the end of this header.
+ */
+ template <typename T>
+ void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources);
+
+ protected slots:
+ void directoryChanged(QString);
+
+ /** Called when the update task is successful.
+ *
+ * This usually calls static_cast on the specific Task type returned by createUpdateTask,
+ * so care must be taken in such cases.
+ * TODO: Figure out a way to express this relationship better without templated classes (Q_OBJECT macro dissalows that).
+ */
+ virtual void onUpdateSucceeded();
+ virtual void onUpdateFailed() {}
+
+ /** Called when the parse task with the given ticket is successful.
+ *
+ * This is just a simple reference implementation. You probably want to override it with your own logic in a subclass
+ * if the resource is complex and has more stuff to parse.
+ */
+ virtual void onParseSucceeded(int ticket, QString resource_id);
+ virtual void onParseFailed(int ticket, QString resource_id) {}
+
+ protected:
+ bool m_can_interact = true;
+
+ QDir m_dir;
+ QFileSystemWatcher m_watcher;
+ bool m_is_watching = false;
+
+ Task::Ptr m_current_update_task = nullptr;
+ bool m_scheduled_update = false;
+
+ QList<Resource::Ptr> m_resources;
+
+ // Represents the relationship between a resource's internal ID and it's row position on the model.
+ QMap<QString, int> m_resources_index;
+
+ QMap<int, Task::Ptr> m_active_parse_tasks;
+ int m_next_resolution_ticket = 0;
+ QMutex m_ticket_mutex;
+};
+
+/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
+#define RESOURCE_HELPERS(T) \
+ [[nodiscard]] T* operator[](size_t index) \
+ { \
+ return static_cast<T*>(m_resources[index].get()); \
+ } \
+ [[nodiscard]] T* at(size_t index) \
+ { \
+ return static_cast<T*>(m_resources[index].get()); \
+ } \
+ [[nodiscard]] const T* at(size_t index) const \
+ { \
+ return static_cast<const T*>(m_resources.at(index).get()); \
+ } \
+ [[nodiscard]] T* first() \
+ { \
+ return static_cast<T*>(m_resources.first().get()); \
+ } \
+ [[nodiscard]] T* last() \
+ { \
+ return static_cast<T*>(m_resources.last().get()); \
+ } \
+ [[nodiscard]] T* find(QString id) \
+ { \
+ auto iter = std::find_if(m_resources.begin(), m_resources.end(), [&](Resource::Ptr r) { return r->internal_id() == id; }); \
+ if (iter == m_resources.end()) \
+ return nullptr; \
+ return static_cast<T*>((*iter).get()); \
+ }
+
+/* Template definition to avoid some code duplication */
+template <typename T>
+void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources)
+{
+ // see if the kept resources changed in some way
+ {
+ QSet<QString> kept_set = current_set;
+ kept_set.intersect(new_set);
+
+ for (auto& kept : kept_set) {
+ auto row = m_resources_index[kept];
+
+ auto new_resource = new_resources[kept];
+ auto current_resource = m_resources[row];
+
+ if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
+ // no significant change, ignore...
+ continue;
+ }
+
+ // If the resource is resolving, but something about it changed, we don't want to
+ // continue the resolving.
+ if (current_resource->isResolving()) {
+ m_active_parse_tasks.remove(current_resource->resolutionTicket());
+ }
+
+ m_resources[row] = new_resource;
+ resolveResource(new_resource);
+ emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
+ }
+ }
+
+ // remove resources no longer present
+ {
+ QSet<QString> removed_set = current_set;
+ removed_set.subtract(new_set);
+
+ QList<int> removed_rows;
+ for (auto& removed : removed_set)
+ removed_rows.append(m_resources_index[removed]);
+
+ std::sort(removed_rows.begin(), removed_rows.end());
+
+ for (auto& removed_index : removed_rows) {
+ beginRemoveRows(QModelIndex(), removed_index, removed_index);
+
+ auto removed_it = m_resources.begin() + removed_index;
+ if ((*removed_it)->isResolving()) {
+ m_active_parse_tasks.remove((*removed_it)->resolutionTicket());
+ }
+
+ m_resources.erase(removed_it);
+
+ endRemoveRows();
+ }
+ }
+
+ // add new resources to the end
+ {
+ QSet<QString> added_set = new_set;
+ added_set.subtract(current_set);
+
+ // When you have a Qt build with assertions turned on, proceeding here will abort the application
+ if (added_set.size() > 0) {
+ beginInsertRows(QModelIndex(), m_resources.size(), m_resources.size() + added_set.size() - 1);
+
+ for (auto& added : added_set) {
+ auto res = new_resources[added];
+ m_resources.append(res);
+ resolveResource(res);
+ }
+
+ endInsertRows();
+ }
+ }
+
+ // update index
+ {
+ m_resources_index.clear();
+ int idx = 0;
+ for (auto mod : m_resources) {
+ m_resources_index[mod->internal_id()] = idx;
+ idx++;
+ }
+ }
+}
diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h
new file mode 100644
index 00000000..0fd5c292
--- /dev/null
+++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <QDir>
+#include <QMap>
+#include <QObject>
+
+#include <memory>
+
+#include "minecraft/mod/Resource.h"
+
+#include "tasks/Task.h"
+
+/** Very simple task that just loads a folder's contents directly.
+ */
+class BasicFolderLoadTask : public Task
+{
+ Q_OBJECT
+public:
+ struct Result {
+ QMap<QString, Resource::Ptr> resources;
+ };
+ using ResultPtr = std::shared_ptr<Result>;
+
+ [[nodiscard]] ResultPtr result() const {
+ return m_result;
+ }
+
+public:
+ BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result) {}
+ void executeTask() override
+ {
+ m_dir.refresh();
+ for (auto entry : m_dir.entryInfoList()) {
+ auto resource = new Resource(entry);
+ m_result->resources.insert(resource->internal_id(), resource);
+ }
+
+ emitSucceeded();
+ }
+
+private:
+ QDir m_dir;
+ ResultPtr m_result;
+};
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index 1519f49d..fe3716ce 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -338,13 +338,13 @@ std::shared_ptr<ModDetails> ReadLiteModInfo(QByteArray contents)
}
-LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile):
+LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile):
+ Task(nullptr, false),
m_token(token),
m_type(type),
m_modFile(modFile),
m_result(new Result())
-{
-}
+{}
void LocalModParseTask::processAsZip()
{
@@ -497,21 +497,21 @@ void LocalModParseTask::processAsLitemod()
zip.close();
}
-void LocalModParseTask::run()
+void LocalModParseTask::executeTask()
{
switch(m_type)
{
- case Mod::MOD_ZIPFILE:
+ case ResourceType::ZIPFILE:
processAsZip();
break;
- case Mod::MOD_FOLDER:
+ case ResourceType::FOLDER:
processAsFolder();
break;
- case Mod::MOD_LITEMOD:
+ case ResourceType::LITEMOD:
processAsLitemod();
break;
default:
break;
}
- emit finished(m_token);
+ emitSucceeded();
}
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h
index ed92394c..dbecb449 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.h
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h
@@ -2,17 +2,17 @@
#include <QDebug>
#include <QObject>
-#include <QRunnable>
#include "minecraft/mod/Mod.h"
#include "minecraft/mod/ModDetails.h"
-class LocalModParseTask : public QObject, public QRunnable
+#include "tasks/Task.h"
+
+class LocalModParseTask : public Task
{
Q_OBJECT
public:
struct Result {
- QString id;
std::shared_ptr<ModDetails> details;
};
using ResultPtr = std::shared_ptr<Result>;
@@ -20,11 +20,10 @@ public:
return m_result;
}
- LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile);
- void run();
+ LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile);
+ void executeTask() override;
-signals:
- void finished(int token);
+ [[nodiscard]] int token() const { return m_token; }
private:
void processAsZip();
@@ -33,7 +32,7 @@ private:
private:
int m_token;
- Mod::ModType m_type;
+ ResourceType m_type;
QFileInfo m_modFile;
ResultPtr m_result;
};
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
index 015ead80..e8180c11 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp
@@ -38,11 +38,11 @@
#include "minecraft/mod/MetadataHandler.h"
-ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan)
- : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result())
+ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan)
+ : Task(nullptr, false), m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result())
{}
-void ModFolderLoadTask::run()
+void ModFolderLoadTask::executeTask()
{
if (m_is_indexed) {
// Read metadata first
@@ -96,7 +96,7 @@ void ModFolderLoadTask::run()
}
}
- emit succeeded();
+ emitSucceeded();
}
void ModFolderLoadTask::getFromMetadata()
diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
index 1f2015d2..86f3f67f 100644
--- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
+++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h
@@ -42,8 +42,9 @@
#include <QRunnable>
#include <memory>
#include "minecraft/mod/Mod.h"
+#include "tasks/Task.h"
-class ModFolderLoadTask : public QObject, public QRunnable
+class ModFolderLoadTask : public Task
{
Q_OBJECT
public:
@@ -56,16 +57,15 @@ public:
}
public:
- ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan = false);
- void run();
-signals:
- void succeeded();
+ ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false);
+
+ void executeTask() override;
private:
void getFromMetadata();
private:
- QDir& m_mods_dir, m_index_dir;
+ QDir m_mods_dir, m_index_dir;
bool m_is_indexed;
bool m_clean_orphan;
ResultPtr m_result;