summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2019-08-04 03:27:53 +0200
committerPetr Mrázek <peterix@gmail.com>2019-08-04 03:27:53 +0200
commita3ffa3d6659e00e0762017cf64911db0183a250c (patch)
tree565b2d69b1d8d7e711682801410ff8a234e3931d
parent7d13e3119801862b9fdf64b11a45c41a64b4fc46 (diff)
downloadMultiMC-a3ffa3d6659e00e0762017cf64911db0183a250c.tar
MultiMC-a3ffa3d6659e00e0762017cf64911db0183a250c.tar.gz
MultiMC-a3ffa3d6659e00e0762017cf64911db0183a250c.tar.lz
MultiMC-a3ffa3d6659e00e0762017cf64911db0183a250c.tar.xz
MultiMC-a3ffa3d6659e00e0762017cf64911db0183a250c.zip
NOISSUE asynchronous, parallel mod folder listing and mod resolving
-rw-r--r--api/logic/CMakeLists.txt20
-rw-r--r--api/logic/MMCZip.h2
-rw-r--r--api/logic/minecraft/MinecraftInstance.cpp26
-rw-r--r--api/logic/minecraft/MinecraftInstance.h20
-rw-r--r--api/logic/minecraft/Mod.cpp433
-rw-r--r--api/logic/minecraft/ParseUtils_test.cpp2
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.h3
-rw-r--r--api/logic/minecraft/mod/LocalModParseTask.cpp298
-rw-r--r--api/logic/minecraft/mod/LocalModParseTask.h37
-rw-r--r--api/logic/minecraft/mod/Mod.cpp169
-rw-r--r--api/logic/minecraft/mod/Mod.h (renamed from api/logic/minecraft/Mod.h)49
-rw-r--r--api/logic/minecraft/mod/ModDetails.h17
-rw-r--r--api/logic/minecraft/mod/ModFolderLoadTask.cpp18
-rw-r--r--api/logic/minecraft/mod/ModFolderLoadTask.h29
-rw-r--r--api/logic/minecraft/mod/ModFolderModel.cpp (renamed from api/logic/minecraft/SimpleModList.cpp)169
-rw-r--r--api/logic/minecraft/mod/ModFolderModel.h (renamed from api/logic/minecraft/SimpleModList.h)22
-rw-r--r--api/logic/minecraft/mod/ModFolderModel_test.cpp (renamed from api/logic/minecraft/SimpleModList_test.cpp)12
-rw-r--r--application/pages/instance/ModFolderPage.cpp26
-rw-r--r--application/pages/instance/ModFolderPage.h8
-rw-r--r--application/pages/instance/VersionPage.cpp2
-rw-r--r--application/widgets/MCModInfoFrame.h2
21 files changed, 821 insertions, 543 deletions
diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt
index 1f795556..a762fb22 100644
--- a/api/logic/CMakeLists.txt
+++ b/api/logic/CMakeLists.txt
@@ -279,15 +279,21 @@ set(MINECRAFT_SOURCES
minecraft/VersionFile.h
minecraft/VersionFilterData.h
minecraft/VersionFilterData.cpp
- minecraft/Mod.h
- minecraft/Mod.cpp
- minecraft/SimpleModList.h
- minecraft/SimpleModList.cpp
minecraft/World.h
minecraft/World.cpp
minecraft/WorldList.h
minecraft/WorldList.cpp
+ minecraft/mod/Mod.h
+ minecraft/mod/Mod.cpp
+ minecraft/mod/ModDetails.h
+ minecraft/mod/ModFolderModel.h
+ minecraft/mod/ModFolderModel.cpp
+ minecraft/mod/ModFolderLoadTask.h
+ minecraft/mod/ModFolderLoadTask.cpp
+ minecraft/mod/LocalModParseTask.h
+ minecraft/mod/LocalModParseTask.cpp
+
# Assets
minecraft/AssetsUtils.h
minecraft/AssetsUtils.cpp
@@ -318,8 +324,8 @@ add_unit_test(Library
)
# FIXME: shares data with FileSystem test
-add_unit_test(SimpleModList
- SOURCES minecraft/SimpleModList_test.cpp
+add_unit_test(ModFolderModel
+ SOURCES minecraft/mod/ModFolderModel_test.cpp
DATA testdata
LIBS MultiMC_logic
)
@@ -479,8 +485,6 @@ set(LOGIC_SOURCES
${FLAME_SOURCES}
)
-message(STATUS "FOO! ${LOGIC_SOURCES}")
-
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
diff --git a/api/logic/MMCZip.h b/api/logic/MMCZip.h
index ee9c5cc1..85ac7802 100644
--- a/api/logic/MMCZip.h
+++ b/api/logic/MMCZip.h
@@ -18,7 +18,7 @@
#include <QString>
#include <QFileInfo>
#include <QSet>
-#include "minecraft/Mod.h"
+#include "minecraft/mod/Mod.h"
#include <functional>
#include "multimc_logic_export.h"
diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp
index 617d7431..9ca77798 100644
--- a/api/logic/minecraft/MinecraftInstance.cpp
+++ b/api/logic/minecraft/MinecraftInstance.cpp
@@ -26,7 +26,7 @@
#include "meta/Index.h"
#include "meta/VersionList.h"
-#include "SimpleModList.h"
+#include "mod/ModFolderModel.h"
#include "WorldList.h"
#include "icons/IIconList.h"
@@ -892,46 +892,46 @@ JavaVersion MinecraftInstance::getJavaVersion() const
return JavaVersion(settings()->get("JavaVersion").toString());
}
-std::shared_ptr<SimpleModList> MinecraftInstance::loaderModList() const
+std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
{
if (!m_loader_mod_list)
{
- m_loader_mod_list.reset(new SimpleModList(loaderModsDir()));
+ m_loader_mod_list.reset(new ModFolderModel(loaderModsDir()));
m_loader_mod_list->disableInteraction(isRunning());
- connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &SimpleModList::disableInteraction);
+ connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
}
return m_loader_mod_list;
}
-std::shared_ptr<SimpleModList> MinecraftInstance::coreModList() const
+std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
{
if (!m_core_mod_list)
{
- m_core_mod_list.reset(new SimpleModList(coreModsDir()));
+ m_core_mod_list.reset(new ModFolderModel(coreModsDir()));
m_core_mod_list->disableInteraction(isRunning());
- connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &SimpleModList::disableInteraction);
+ connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
}
return m_core_mod_list;
}
-std::shared_ptr<SimpleModList> MinecraftInstance::resourcePackList() const
+std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
{
if (!m_resource_pack_list)
{
- m_resource_pack_list.reset(new SimpleModList(resourcePacksDir()));
+ m_resource_pack_list.reset(new ModFolderModel(resourcePacksDir()));
m_resource_pack_list->disableInteraction(isRunning());
- connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &SimpleModList::disableInteraction);
+ connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction);
}
return m_resource_pack_list;
}
-std::shared_ptr<SimpleModList> MinecraftInstance::texturePackList() const
+std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
{
if (!m_texture_pack_list)
{
- m_texture_pack_list.reset(new SimpleModList(texturePacksDir()));
+ m_texture_pack_list.reset(new ModFolderModel(texturePacksDir()));
m_texture_pack_list->disableInteraction(isRunning());
- connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &SimpleModList::disableInteraction);
+ connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction);
}
return m_texture_pack_list;
}
diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h
index 501697f7..dd14664f 100644
--- a/api/logic/minecraft/MinecraftInstance.h
+++ b/api/logic/minecraft/MinecraftInstance.h
@@ -1,13 +1,13 @@
#pragma once
#include "BaseInstance.h"
#include <java/JavaVersion.h>
-#include "minecraft/Mod.h"
+#include "minecraft/mod/Mod.h"
#include <QProcess>
#include <QDir>
#include "multimc_logic_export.h"
class ModsModel;
-class SimpleModList;
+class ModFolderModel;
class WorldList;
class GameOptions;
class LaunchStep;
@@ -69,10 +69,10 @@ public:
////// Mod Lists //////
std::shared_ptr<ModsModel> modsModel() const;
- std::shared_ptr<SimpleModList> loaderModList() const;
- std::shared_ptr<SimpleModList> coreModList() const;
- std::shared_ptr<SimpleModList> resourcePackList() const;
- std::shared_ptr<SimpleModList> texturePackList() const;
+ std::shared_ptr<ModFolderModel> loaderModList() const;
+ std::shared_ptr<ModFolderModel> coreModList() const;
+ std::shared_ptr<ModFolderModel> resourcePackList() const;
+ std::shared_ptr<ModFolderModel> texturePackList() const;
std::shared_ptr<WorldList> worldList() const;
std::shared_ptr<GameOptions> gameOptionsModel() const;
@@ -124,10 +124,10 @@ private:
protected: // data
std::shared_ptr<ComponentList> m_components;
mutable std::shared_ptr<ModsModel> m_mods_model;
- mutable std::shared_ptr<SimpleModList> m_loader_mod_list;
- mutable std::shared_ptr<SimpleModList> m_core_mod_list;
- mutable std::shared_ptr<SimpleModList> m_resource_pack_list;
- mutable std::shared_ptr<SimpleModList> m_texture_pack_list;
+ mutable std::shared_ptr<ModFolderModel> m_loader_mod_list;
+ mutable std::shared_ptr<ModFolderModel> m_core_mod_list;
+ mutable std::shared_ptr<ModFolderModel> m_resource_pack_list;
+ mutable std::shared_ptr<ModFolderModel> m_texture_pack_list;
mutable std::shared_ptr<WorldList> m_world_list;
mutable std::shared_ptr<GameOptions> m_game_options;
};
diff --git a/api/logic/minecraft/Mod.cpp b/api/logic/minecraft/Mod.cpp
deleted file mode 100644
index 936ca00a..00000000
--- a/api/logic/minecraft/Mod.cpp
+++ /dev/null
@@ -1,433 +0,0 @@
-/* Copyright 2013-2019 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <QDir>
-#include <QString>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonArray>
-#include <QJsonValue>
-#include <quazip.h>
-#include <quazipfile.h>
-
-#include "Mod.h"
-#include "settings/INIFile.h"
-#include <FileSystem.h>
-#include <QDebug>
-
-namespace {
-// NEW format
-// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
-
-// OLD format:
-// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
-ModDetails ReadMCModInfo(QByteArray contents)
-{
- auto getInfoFromArray = [&](QJsonArray arr)->ModDetails
- {
- ModDetails details;
- if (!arr.at(0).isObject()) {
- return details;
- }
- auto firstObj = arr.at(0).toObject();
- details.mod_id = firstObj.value("modid").toString();
- auto name = firstObj.value("name").toString();
- // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name
- if(name != "Example Mod") {
- details.name = name;
- }
- details.version = firstObj.value("version").toString();
- details.updateurl = firstObj.value("updateUrl").toString();
- auto homeurl = firstObj.value("url").toString().trimmed();
- if(!homeurl.isEmpty())
- {
- // fix up url.
- if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://"))
- {
- homeurl.prepend("http://");
- }
- }
- details.homeurl = homeurl;
- details.description = firstObj.value("description").toString();
- QJsonArray authors = firstObj.value("authorList").toArray();
- if (authors.size() == 0) {
- // FIXME: what is the format of this? is there any?
- authors = firstObj.value("authors").toArray();
- }
-
- for (auto author: authors)
- {
- details.authors.append(author.toString());
- }
- details.credits = firstObj.value("credits").toString();
- details.valid = true;
- return details;
- };
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
- // this is the very old format that had just the array
- if (jsonDoc.isArray())
- {
- return getInfoFromArray(jsonDoc.array());
- }
- else if (jsonDoc.isObject())
- {
- auto val = jsonDoc.object().value("modinfoversion");
- if(val.isUndefined()) {
- val = jsonDoc.object().value("modListVersion");
- }
- int version = val.toDouble();
- if (version != 2)
- {
- qCritical() << "BAD stuff happened to mod json:";
- qCritical() << contents;
- return ModDetails();
- }
- auto arrVal = jsonDoc.object().value("modlist");
- if(arrVal.isUndefined()) {
- arrVal = jsonDoc.object().value("modList");
- }
- if (arrVal.isArray())
- {
- return getInfoFromArray(arrVal.toArray());
- }
- }
- return ModDetails();
-}
-
-// https://fabricmc.net/wiki/documentation:fabric_mod_json
-ModDetails ReadFabricModInfo(QByteArray contents)
-{
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
- auto object = jsonDoc.object();
- auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0;
-
- ModDetails details;
-
- details.mod_id = object.value("id").toString();
- details.version = object.value("version").toString();
-
- details.name = object.contains("name") ? object.value("name").toString() : details.mod_id;
- details.description = object.value("description").toString();
-
- if (schemaVersion >= 1)
- {
- QJsonArray authors = object.value("authors").toArray();
- for (auto author: authors)
- {
- if(author.isObject()) {
- details.authors.append(author.toObject().value("name").toString());
- }
- else {
- details.authors.append(author.toString());
- }
- }
-
- if (object.contains("contact"))
- {
- QJsonObject contact = object.value("contact").toObject();
-
- if (contact.contains("homepage"))
- {
- details.homeurl = contact.value("homepage").toString();
- }
- }
- }
- details.valid = !details.name.isEmpty();
- return details;
-}
-
-ModDetails ReadForgeInfo(QByteArray contents)
-{
- ModDetails details;
- // Read the data
- details.name = "Minecraft Forge";
- details.mod_id = "Forge";
- details.homeurl = "http://www.minecraftforge.net/forum/";
- details.valid = true;
- INIFile ini;
- if (!ini.loadFile(contents))
- return details;
-
- QString major = ini.get("forge.major.number", "0").toString();
- QString minor = ini.get("forge.minor.number", "0").toString();
- QString revision = ini.get("forge.revision.number", "0").toString();
- QString build = ini.get("forge.build.number", "0").toString();
-
- details.version = major + "." + minor + "." + revision + "." + build;
- return details;
-}
-
-ModDetails ReadLiteModInfo(QByteArray contents)
-{
- ModDetails details;
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
- auto object = jsonDoc.object();
- if (object.contains("name"))
- {
- details.mod_id = details.name = object.value("name").toString();
- }
- if (object.contains("version"))
- {
- details.version = object.value("version").toString("");
- }
- else
- {
- details.version = object.value("revision").toString("");
- }
- details.mcversion = object.value("mcversion").toString();
- auto author = object.value("author").toString();
- if(!author.isEmpty()) {
- details.authors.append(author);
- }
- details.description = object.value("description").toString();
- details.homeurl = object.value("url").toString();
- return details;
-}
-
-ModDetails invalidDetails;
-
-}
-
-
-Mod::Mod(const QFileInfo &file)
-{
- repath(file);
- m_changedDateTime = file.lastModified();
-}
-
-void Mod::repath(const QFileInfo &file)
-{
- m_file = file;
- QString name_base = file.fileName();
-
- m_type = Mod::MOD_UNKNOWN;
-
- if (m_file.isDir())
- {
- m_type = MOD_FOLDER;
- m_name = name_base;
- m_mmc_id = name_base;
- }
- else if (m_file.isFile())
- {
- if (name_base.endsWith(".disabled"))
- {
- m_enabled = false;
- name_base.chop(9);
- }
- else
- {
- m_enabled = true;
- }
- m_mmc_id = name_base;
- if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
- {
- m_type = MOD_ZIPFILE;
- name_base.chop(4);
- }
- else if (name_base.endsWith(".litemod"))
- {
- m_type = MOD_LITEMOD;
- name_base.chop(8);
- }
- else
- {
- m_type = MOD_SINGLEFILE;
- }
- m_name = name_base;
- }
-
- if (m_type == MOD_ZIPFILE)
- {
- QuaZip zip(m_file.filePath());
- if (!zip.open(QuaZip::mdUnzip))
- return;
-
- QuaZipFile file(&zip);
-
- if (zip.setCurrentFile("mcmod.info"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- m_localDetails = ReadMCModInfo(file.readAll());
- file.close();
- zip.close();
- return;
- }
- else if (zip.setCurrentFile("fabric.mod.json"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- m_localDetails = ReadFabricModInfo(file.readAll());
- file.close();
- zip.close();
- return;
- }
- else if (zip.setCurrentFile("forgeversion.properties"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- m_localDetails = ReadForgeInfo(file.readAll());
- file.close();
- zip.close();
- return;
- }
-
- zip.close();
- }
- else if (m_type == MOD_FOLDER)
- {
- QFileInfo mcmod_info(FS::PathCombine(m_file.filePath(), "mcmod.info"));
- if (mcmod_info.isFile())
- {
- QFile mcmod(mcmod_info.filePath());
- if (!mcmod.open(QIODevice::ReadOnly))
- return;
- auto data = mcmod.readAll();
- if (data.isEmpty() || data.isNull())
- return;
- m_localDetails = ReadMCModInfo(data);
- }
- }
- else if (m_type == MOD_LITEMOD)
- {
- QuaZip zip(m_file.filePath());
- if (!zip.open(QuaZip::mdUnzip))
- return;
-
- QuaZipFile file(&zip);
-
- if (zip.setCurrentFile("litemod.json"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- m_localDetails = ReadLiteModInfo(file.readAll());
- file.close();
- }
- zip.close();
- }
-}
-
-bool Mod::enable(bool value)
-{
- if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
- return false;
-
- if (m_enabled == value)
- return false;
-
- QString path = m_file.absoluteFilePath();
- if (value)
- {
- QFile foo(path);
- if (!path.endsWith(".disabled"))
- return false;
- path.chop(9);
- if (!foo.rename(path))
- return false;
- }
- else
- {
- QFile foo(path);
- path += ".disabled";
- if (!foo.rename(path))
- return false;
- }
- m_file = QFileInfo(path);
- m_enabled = value;
- return true;
-}
-
-bool Mod::destroy()
-{
- if (m_type == MOD_FOLDER)
- {
- QDir d(m_file.filePath());
- if (d.removeRecursively())
- {
- m_type = MOD_UNKNOWN;
- return true;
- }
- return false;
- }
- else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD)
- {
- QFile f(m_file.filePath());
- if (f.remove())
- {
- m_type = MOD_UNKNOWN;
- return true;
- }
- return false;
- }
- return true;
-}
-
-
-const ModDetails & Mod::details() const
-{
- if(!m_localDetails)
- return invalidDetails;
- return m_localDetails;
-}
-
-
-QString Mod::version() const
-{
- return details().version;
-}
-
-QString Mod::name() const
-{
- auto & d = details();
- if(d && !d.name.isEmpty()) {
- return d.name;
- }
- return m_name;
-}
-
-QString Mod::homeurl() const
-{
- return details().homeurl;
-}
-
-QString Mod::description() const
-{
- return details().description;
-}
-
-QStringList Mod::authors() const
-{
- return details().authors;
-}
diff --git a/api/logic/minecraft/ParseUtils_test.cpp b/api/logic/minecraft/ParseUtils_test.cpp
index fde9cdbf..fcc137e5 100644
--- a/api/logic/minecraft/ParseUtils_test.cpp
+++ b/api/logic/minecraft/ParseUtils_test.cpp
@@ -33,7 +33,7 @@ slots:
auto time_parsed = timeFromS3Time(timestamp);
auto time_serialized = timeToS3Time(time_parsed);
-
+
QCOMPARE(time_serialized, timestamp);
}
diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h
index 46fca3e4..7c0b94e8 100644
--- a/api/logic/minecraft/legacy/LegacyInstance.h
+++ b/api/logic/minecraft/legacy/LegacyInstance.h
@@ -16,12 +16,11 @@
#pragma once
#include "BaseInstance.h"
-#include "minecraft/Mod.h"
#include "launch/LaunchTask.h"
#include "multimc_logic_export.h"
-class SimpleModList;
+class ModFolderModel;
class LegacyModList;
class WorldList;
class Task;
diff --git a/api/logic/minecraft/mod/LocalModParseTask.cpp b/api/logic/minecraft/mod/LocalModParseTask.cpp
new file mode 100644
index 00000000..22ebd7d4
--- /dev/null
+++ b/api/logic/minecraft/mod/LocalModParseTask.cpp
@@ -0,0 +1,298 @@
+#include "LocalModParseTask.h"
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonValue>
+#include <quazip.h>
+#include <quazipfile.h>
+
+#include "settings/INIFile.h"
+#include "FileSystem.h"
+
+namespace {
+
+// NEW format
+// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
+
+// OLD format:
+// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
+std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
+{
+ auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr<ModDetails>
+ {
+ if (!arr.at(0).isObject()) {
+ return nullptr;
+ }
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ auto firstObj = arr.at(0).toObject();
+ details->mod_id = firstObj.value("modid").toString();
+ auto name = firstObj.value("name").toString();
+ // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name
+ if(name != "Example Mod") {
+ details->name = name;
+ }
+ details->version = firstObj.value("version").toString();
+ details->updateurl = firstObj.value("updateUrl").toString();
+ auto homeurl = firstObj.value("url").toString().trimmed();
+ if(!homeurl.isEmpty())
+ {
+ // fix up url.
+ if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://"))
+ {
+ homeurl.prepend("http://");
+ }
+ }
+ details->homeurl = homeurl;
+ details->description = firstObj.value("description").toString();
+ QJsonArray authors = firstObj.value("authorList").toArray();
+ if (authors.size() == 0) {
+ // FIXME: what is the format of this? is there any?
+ authors = firstObj.value("authors").toArray();
+ }
+
+ for (auto author: authors)
+ {
+ details->authors.append(author.toString());
+ }
+ details->credits = firstObj.value("credits").toString();
+ return details;
+ };
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ // this is the very old format that had just the array
+ if (jsonDoc.isArray())
+ {
+ return getInfoFromArray(jsonDoc.array());
+ }
+ else if (jsonDoc.isObject())
+ {
+ auto val = jsonDoc.object().value("modinfoversion");
+ if(val.isUndefined()) {
+ val = jsonDoc.object().value("modListVersion");
+ }
+ int version = val.toDouble();
+ if (version != 2)
+ {
+ qCritical() << "BAD stuff happened to mod json:";
+ qCritical() << contents;
+ return nullptr;
+ }
+ auto arrVal = jsonDoc.object().value("modlist");
+ if(arrVal.isUndefined()) {
+ arrVal = jsonDoc.object().value("modList");
+ }
+ if (arrVal.isArray())
+ {
+ return getInfoFromArray(arrVal.toArray());
+ }
+ }
+ return nullptr;
+}
+
+// https://fabricmc.net/wiki/documentation:fabric_mod_json
+std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
+{
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ auto object = jsonDoc.object();
+ auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0;
+
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+
+ details->mod_id = object.value("id").toString();
+ details->version = object.value("version").toString();
+
+ details->name = object.contains("name") ? object.value("name").toString() : details->mod_id;
+ details->description = object.value("description").toString();
+
+ if (schemaVersion >= 1)
+ {
+ QJsonArray authors = object.value("authors").toArray();
+ for (auto author: authors)
+ {
+ if(author.isObject()) {
+ details->authors.append(author.toObject().value("name").toString());
+ }
+ else {
+ details->authors.append(author.toString());
+ }
+ }
+
+ if (object.contains("contact"))
+ {
+ QJsonObject contact = object.value("contact").toObject();
+
+ if (contact.contains("homepage"))
+ {
+ details->homeurl = contact.value("homepage").toString();
+ }
+ }
+ }
+ return details;
+}
+
+std::shared_ptr<ModDetails> ReadForgeInfo(QByteArray contents)
+{
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ // Read the data
+ details->name = "Minecraft Forge";
+ details->mod_id = "Forge";
+ details->homeurl = "http://www.minecraftforge.net/forum/";
+ INIFile ini;
+ if (!ini.loadFile(contents))
+ return details;
+
+ QString major = ini.get("forge.major.number", "0").toString();
+ QString minor = ini.get("forge.minor.number", "0").toString();
+ QString revision = ini.get("forge.revision.number", "0").toString();
+ QString build = ini.get("forge.build.number", "0").toString();
+
+ details->version = major + "." + minor + "." + revision + "." + build;
+ return details;
+}
+
+std::shared_ptr<ModDetails> ReadLiteModInfo(QByteArray contents)
+{
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ auto object = jsonDoc.object();
+ if (object.contains("name"))
+ {
+ details->mod_id = details->name = object.value("name").toString();
+ }
+ if (object.contains("version"))
+ {
+ details->version = object.value("version").toString("");
+ }
+ else
+ {
+ details->version = object.value("revision").toString("");
+ }
+ details->mcversion = object.value("mcversion").toString();
+ auto author = object.value("author").toString();
+ if(!author.isEmpty()) {
+ details->authors.append(author);
+ }
+ details->description = object.value("description").toString();
+ details->homeurl = object.value("url").toString();
+ return details;
+}
+
+}
+
+LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile):
+ m_token(token),
+ m_type(type),
+ m_modFile(modFile),
+ m_result(new Result())
+{
+}
+
+void LocalModParseTask::processAsZip()
+{
+ QuaZip zip(m_modFile.filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return;
+
+ QuaZipFile file(&zip);
+
+ if (zip.setCurrentFile("mcmod.info"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadMCModInfo(file.readAll());
+ file.close();
+ zip.close();
+ return;
+ }
+ else if (zip.setCurrentFile("fabric.mod.json"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadFabricModInfo(file.readAll());
+ file.close();
+ zip.close();
+ return;
+ }
+ else if (zip.setCurrentFile("forgeversion.properties"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadForgeInfo(file.readAll());
+ file.close();
+ zip.close();
+ return;
+ }
+
+ zip.close();
+}
+
+void LocalModParseTask::processAsFolder()
+{
+ QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info"));
+ if (mcmod_info.isFile())
+ {
+ QFile mcmod(mcmod_info.filePath());
+ if (!mcmod.open(QIODevice::ReadOnly))
+ return;
+ auto data = mcmod.readAll();
+ if (data.isEmpty() || data.isNull())
+ return;
+ m_result->details = ReadMCModInfo(data);
+ }
+}
+
+void LocalModParseTask::processAsLitemod()
+{
+ QuaZip zip(m_modFile.filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return;
+
+ QuaZipFile file(&zip);
+
+ if (zip.setCurrentFile("litemod.json"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadLiteModInfo(file.readAll());
+ file.close();
+ }
+ zip.close();
+}
+
+void LocalModParseTask::run()
+{
+ switch(m_type)
+ {
+ case Mod::MOD_ZIPFILE:
+ processAsZip();
+ break;
+ case Mod::MOD_FOLDER:
+ processAsFolder();
+ break;
+ case Mod::MOD_LITEMOD:
+ processAsLitemod();
+ break;
+ default:
+ break;
+ }
+ emit finished(m_token);
+}
diff --git a/api/logic/minecraft/mod/LocalModParseTask.h b/api/logic/minecraft/mod/LocalModParseTask.h
new file mode 100644
index 00000000..0f119ba6
--- /dev/null
+++ b/api/logic/minecraft/mod/LocalModParseTask.h
@@ -0,0 +1,37 @@
+#pragma once
+#include <QRunnable>
+#include <QDebug>
+#include <QObject>
+#include "Mod.h"
+#include "ModDetails.h"
+
+class LocalModParseTask : public QObject, public QRunnable
+{
+ Q_OBJECT
+public:
+ struct Result {
+ QString id;
+ std::shared_ptr<ModDetails> details;
+ };
+ using ResultPtr = std::shared_ptr<Result>;
+ ResultPtr result() const {
+ return m_result;
+ }
+
+ LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile);
+ void run();
+
+signals:
+ void finished(int token);
+
+private:
+ void processAsZip();
+ void processAsFolder();
+ void processAsLitemod();
+
+private:
+ int m_token;
+ Mod::ModType m_type;
+ QFileInfo m_modFile;
+ ResultPtr m_result;
+};
diff --git a/api/logic/minecraft/mod/Mod.cpp b/api/logic/minecraft/mod/Mod.cpp
new file mode 100644
index 00000000..aa2496c2
--- /dev/null
+++ b/api/logic/minecraft/mod/Mod.cpp
@@ -0,0 +1,169 @@
+/* Copyright 2013-2019 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <QDir>
+#include <QString>
+
+#include "Mod.h"
+#include <QDebug>
+
+namespace {
+
+ModDetails invalidDetails;
+
+}
+
+
+Mod::Mod(const QFileInfo &file)
+{
+ repath(file);
+ m_changedDateTime = file.lastModified();
+}
+
+void Mod::repath(const QFileInfo &file)
+{
+ m_file = file;
+ QString name_base = file.fileName();
+
+ m_type = Mod::MOD_UNKNOWN;
+
+ m_mmc_id = name_base;
+
+ if (m_file.isDir())
+ {
+ m_type = MOD_FOLDER;
+ m_name = name_base;
+ }
+ else if (m_file.isFile())
+ {
+ if (name_base.endsWith(".disabled"))
+ {
+ m_enabled = false;
+ name_base.chop(9);
+ }
+ else
+ {
+ m_enabled = true;
+ }
+ if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
+ {
+ m_type = MOD_ZIPFILE;
+ name_base.chop(4);
+ }
+ else if (name_base.endsWith(".litemod"))
+ {
+ m_type = MOD_LITEMOD;
+ name_base.chop(8);
+ }
+ else
+ {
+ m_type = MOD_SINGLEFILE;
+ }
+ m_name = name_base;
+ }
+}
+
+bool Mod::enable(bool value)
+{
+ if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
+ return false;
+
+ if (m_enabled == value)
+ return false;
+
+ QString path = m_file.absoluteFilePath();
+ if (value)
+ {
+ QFile foo(path);
+ if (!path.endsWith(".disabled"))
+ return false;
+ path.chop(9);
+ if (!foo.rename(path))
+ return false;
+ }
+ else
+ {
+ QFile foo(path);
+ path += ".disabled";
+ if (!foo.rename(path))
+ return false;
+ }
+ m_file = QFileInfo(path);
+ m_enabled = value;
+ return true;
+}
+
+bool Mod::destroy()
+{
+ if (m_type == MOD_FOLDER)
+ {
+ QDir d(m_file.filePath());
+ if (d.removeRecursively())
+ {
+ m_type = MOD_UNKNOWN;
+ return true;
+ }
+ return false;
+ }
+ else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD)
+ {
+ QFile f(m_file.filePath());
+ if (f.remove())
+ {
+ m_type = MOD_UNKNOWN;
+ return true;
+ }
+ return false;
+ }
+ return true;
+}
+
+
+const ModDetails & Mod::details() const
+{
+ if(!m_localDetails)
+ return invalidDetails;
+ return *m_localDetails;
+}
+
+
+QString Mod::version() const
+{
+ return details().version;
+}
+
+QString Mod::name() const
+{
+ auto & d = details();
+ if(!d.name.isEmpty()) {
+ return d.name;
+ }
+ return m_name;
+}
+
+QString Mod::homeurl() const
+{
+ return details().homeurl;
+}
+
+QString Mod::description() const
+{
+ return details().description;
+}
+
+QStringList Mod::authors() const
+{
+ return details().authors;
+}
diff --git a/api/logic/minecraft/Mod.h b/api/logic/minecraft/mod/Mod.h
index 890669ce..d787fb48 100644
--- a/api/logic/minecraft/Mod.h
+++ b/api/logic/minecraft/mod/Mod.h
@@ -16,24 +16,14 @@
#pragma once
#include <QFileInfo>
#include <QDateTime>
+#include <QList>
+#include <memory>
+
#include "multimc_logic_export.h"
-struct ModDetails
-{
- operator bool() const {
- return valid;
- }
- bool valid = false;
- QString mod_id;
- QString name;
- QString version;
- QString mcversion;
- QString homeurl;
- QString updateurl;
- QString description;
- QStringList authors;
- QString credits;
-};
+#include "ModDetails.h"
+
+
class MULTIMC_LOGIC_EXPORT Mod
{
@@ -47,6 +37,7 @@ public:
MOD_LITEMOD, //!< The mod is a litemod
};
+ Mod() = default;
Mod(const QFileInfo &file);
QFileInfo filename() const
@@ -92,13 +83,35 @@ public:
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
+ bool shouldResolve() {
+ return !m_resolving && !m_resolved;
+ }
+ bool isResolving() {
+ return m_resolving;
+ }
+ int resolutionTicket()
+ {
+ return m_resolutionTicket;
+ }
+ void setResolving(bool resolving, int resolutionTicket) {
+ m_resolving = resolving;
+ m_resolutionTicket = resolutionTicket;
+ }
+ void finishResolvingWithDetails(std::shared_ptr<ModDetails> details){
+ m_resolving = false;
+ m_resolved = true;
+ m_localDetails = details;
+ }
+
protected:
QFileInfo m_file;
QDateTime m_changedDateTime;
QString m_mmc_id;
QString m_name;
bool m_enabled = true;
+ bool m_resolving = false;
+ bool m_resolved = false;
+ int m_resolutionTicket = 0;
ModType m_type = MOD_UNKNOWN;
- bool m_bare = true;
- ModDetails m_localDetails;
+ std::shared_ptr<ModDetails> m_localDetails;
};
diff --git a/api/logic/minecraft/mod/ModDetails.h b/api/logic/minecraft/mod/ModDetails.h
new file mode 100644
index 00000000..6ab4aee7
--- /dev/null
+++ b/api/logic/minecraft/mod/ModDetails.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <QString>
+#include <QStringList>
+
+struct ModDetails
+{
+ QString mod_id;
+ QString name;
+ QString version;
+ QString mcversion;
+ QString homeurl;
+ QString updateurl;
+ QString description;
+ QStringList authors;
+ QString credits;
+};
diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.cpp b/api/logic/minecraft/mod/ModFolderLoadTask.cpp
new file mode 100644
index 00000000..88349877
--- /dev/null
+++ b/api/logic/minecraft/mod/ModFolderLoadTask.cpp
@@ -0,0 +1,18 @@
+#include "ModFolderLoadTask.h"
+#include <QDebug>
+
+ModFolderLoadTask::ModFolderLoadTask(QDir dir) :
+ m_dir(dir), m_result(new Result())
+{
+}
+
+void ModFolderLoadTask::run()
+{
+ m_dir.refresh();
+ for (auto entry : m_dir.entryInfoList())
+ {
+ Mod m(entry);
+ m_result->mods[m.mmc_id()] = m;
+ }
+ emit succeeded();
+}
diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.h b/api/logic/minecraft/mod/ModFolderLoadTask.h
new file mode 100644
index 00000000..8d720e65
--- /dev/null
+++ b/api/logic/minecraft/mod/ModFolderLoadTask.h
@@ -0,0 +1,29 @@
+#pragma once
+#include <QRunnable>
+#include <QObject>
+#include <QDir>
+#include <QMap>
+#include "Mod.h"
+#include <memory>
+
+class ModFolderLoadTask : public QObject, public QRunnable
+{
+ Q_OBJECT
+public:
+ struct Result {
+ QMap<QString, Mod> mods;
+ };
+ using ResultPtr = std::shared_ptr<Result>;
+ ResultPtr result() const {
+ return m_result;
+ }
+
+public:
+ ModFolderLoadTask(QDir dir);
+ void run();
+signals:
+ void succeeded();
+private:
+ QDir m_dir;
+ ResultPtr m_result;
+};
diff --git a/api/logic/minecraft/SimpleModList.cpp b/api/logic/minecraft/mod/ModFolderModel.cpp
index b90b55c2..7293f837 100644
--- a/api/logic/minecraft/SimpleModList.cpp
+++ b/api/logic/minecraft/mod/ModFolderModel.cpp
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-#include "SimpleModList.h"
+#include "ModFolderModel.h"
#include <FileSystem.h>
#include <QMimeData>
#include <QUrl>
@@ -21,8 +21,12 @@
#include <QString>
#include <QFileSystemWatcher>
#include <QDebug>
+#include "ModFolderLoadTask.h"
+#include <QThreadPool>
+#include <algorithm>
+#include "LocalModParseTask.h"
-SimpleModList::SimpleModList(const QString &dir) : QAbstractListModel(), m_dir(dir)
+ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks);
@@ -31,7 +35,7 @@ SimpleModList::SimpleModList(const QString &dir) : QAbstractListModel(), m_dir(d
connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
}
-void SimpleModList::startWatching()
+void ModFolderModel::startWatching()
{
if(is_watching)
return;
@@ -49,7 +53,7 @@ void SimpleModList::startWatching()
}
}
-void SimpleModList::stopWatching()
+void ModFolderModel::stopWatching()
{
if(!is_watching)
return;
@@ -65,27 +69,136 @@ void SimpleModList::stopWatching()
}
}
-bool SimpleModList::update()
+bool ModFolderModel::update()
{
- if (!isValid())
+ if (!isValid()) {
return false;
+ }
+ if(m_update) {
+ scheduled_update = true;
+ }
+
+ auto task = new ModFolderLoadTask(m_dir);
+ m_update = task->result();
+ QThreadPool *threadPool = QThreadPool::globalInstance();
+ connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::updateFinished);
+ threadPool->start(task);
+ return true;
+}
+
+void ModFolderModel::updateFinished()
+{
+ QSet<QString> currentSet = modsIndex.keys().toSet();
+ auto & newMods = m_update->mods;
+ QSet<QString> newSet = newMods.keys().toSet();
+
+ // 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());
+ }
+ oldMod = 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());
+ for(auto iter = removedRows.rbegin(); iter != removedRows.rend(); iter++) {
+ int removedIndex = *iter;
+ beginRemoveRows(QModelIndex(), removedIndex, removedIndex);
+ auto removedIter = mods.begin() + removedIndex;
+ if(removedIter->isResolving()) {
+ activeTickets.remove(removedIter->resolutionTicket());
+ }
+ mods.erase(removedIter);
+ endRemoveRows();
+ }
+ }
- QList<Mod> newMods;
- m_dir.refresh();
- for (auto entry : m_dir.entryInfoList())
+ // add new mods to the end
{
- newMods.append(Mod(entry));
+ QSet<QString> added = newSet;
+ added.subtract(currentSet);
+ 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.mmc_id()] = idx;
+ idx++;
+ }
}
- beginResetModel();
- mods.swap(newMods);
- endResetModel();
+ m_update.reset();
emit changed();
- return true;
+
+ if(scheduled_update) {
+ scheduled_update = false;
+ update();
+ }
+}
+
+void ModFolderModel::resolveMod(Mod& m)
+{
+ if(!m.shouldResolve()) {
+ return;
+ }
+
+ auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename());
+ auto result = task->result();
+ result->id = m.mmc_id();
+ activeTickets.insert(nextResolutionTicket, result);
+ m.setResolving(true, nextResolutionTicket);
+ nextResolutionTicket++;
+ QThreadPool *threadPool = QThreadPool::globalInstance();
+ connect(task, &LocalModParseTask::finished, this, &ModFolderModel::modParseFinished);
+ threadPool->start(task);
+}
+
+void ModFolderModel::modParseFinished(int token)
+{
+ auto iter = activeTickets.find(token);
+ if(iter == activeTickets.end()) {
+ return;
+ }
+ auto result = *iter;
+ activeTickets.remove(token);
+ int row = modsIndex[result->id];
+ auto & mod = mods[row];
+ mod.finishResolvingWithDetails(result->details);
+ emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
-void SimpleModList::disableInteraction(bool disabled)
+void ModFolderModel::disableInteraction(bool disabled)
{
if (interaction_disabled == disabled) {
return;
@@ -96,18 +209,18 @@ void SimpleModList::disableInteraction(bool disabled)
}
}
-void SimpleModList::directoryChanged(QString path)
+void ModFolderModel::directoryChanged(QString path)
{
update();
}
-bool SimpleModList::isValid()
+bool ModFolderModel::isValid()
{
return m_dir.exists() && m_dir.isReadable();
}
// FIXME: this does not take disabled mod (with extra .disable extension) into account...
-bool SimpleModList::installMod(const QString &filename)
+bool ModFolderModel::installMod(const QString &filename)
{
if(interaction_disabled) {
return false;
@@ -189,7 +302,7 @@ bool SimpleModList::installMod(const QString &filename)
return false;
}
-bool SimpleModList::enableMods(const QModelIndexList& indexes, bool enable)
+bool ModFolderModel::enableMods(const QModelIndexList& indexes, bool enable)
{
if(interaction_disabled) {
return false;
@@ -208,7 +321,7 @@ bool SimpleModList::enableMods(const QModelIndexList& indexes, bool enable)
return true;
}
-bool SimpleModList::deleteMods(const QModelIndexList& indexes)
+bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
{
if(interaction_disabled) {
return false;
@@ -226,12 +339,12 @@ bool SimpleModList::deleteMods(const QModelIndexList& indexes)
return true;
}
-int SimpleModList::columnCount(const QModelIndex &parent) const
+int ModFolderModel::columnCount(const QModelIndex &parent) const
{
return NUM_COLUMNS;
}
-QVariant SimpleModList::data(const QModelIndex &index, int role) const
+QVariant ModFolderModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
@@ -283,7 +396,7 @@ QVariant SimpleModList::data(const QModelIndex &index, int role) const
}
}
-bool SimpleModList::setData(const QModelIndex &index, const QVariant &value, int role)
+bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
{
@@ -302,7 +415,7 @@ bool SimpleModList::setData(const QModelIndex &index, const QVariant &value, int
return false;
}
-QVariant SimpleModList::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
{
switch (role)
{
@@ -341,7 +454,7 @@ QVariant SimpleModList::headerData(int section, Qt::Orientation orientation, int
return QVariant();
}
-Qt::ItemFlags SimpleModList::flags(const QModelIndex &index) const
+Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
auto flags = defaultFlags;
@@ -358,20 +471,20 @@ Qt::ItemFlags SimpleModList::flags(const QModelIndex &index) const
return flags;
}
-Qt::DropActions SimpleModList::supportedDropActions() const
+Qt::DropActions ModFolderModel::supportedDropActions() const
{
// copy from outside, move from within and other mod lists
return Qt::CopyAction | Qt::MoveAction;
}
-QStringList SimpleModList::mimeTypes() const
+QStringList ModFolderModel::mimeTypes() const
{
QStringList types;
types << "text/uri-list";
return types;
}
-bool SimpleModList::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
+bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
{
if (action == Qt::IgnoreAction)
{
diff --git a/api/logic/minecraft/SimpleModList.h b/api/logic/minecraft/mod/ModFolderModel.h
index 8cb57727..776c0c87 100644
--- a/api/logic/minecraft/SimpleModList.h
+++ b/api/logic/minecraft/mod/ModFolderModel.h
@@ -16,13 +16,17 @@
#pragma once
#include <QList>
+#include <QMap>
+#include <QSet>
#include <QString>
#include <QDir>
#include <QAbstractListModel>
-#include "minecraft/Mod.h"
+#include "Mod.h"
#include "multimc_logic_export.h"
+#include "ModFolderLoadTask.h"
+#include "LocalModParseTask.h"
class LegacyInstance;
class BaseInstance;
@@ -32,7 +36,7 @@ class QFileSystemWatcher;
* A legacy mod list.
* Backed by a folder.
*/
-class MULTIMC_LOGIC_EXPORT SimpleModList : public QAbstractListModel
+class MULTIMC_LOGIC_EXPORT ModFolderModel : public QAbstractListModel
{
Q_OBJECT
public:
@@ -40,11 +44,11 @@ public:
{
ActiveColumn = 0,
NameColumn,
- DateColumn,
VersionColumn,
+ DateColumn,
NUM_COLUMNS
};
- SimpleModList(const QString &dir);
+ ModFolderModel(const QString &dir);
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;
@@ -112,14 +116,24 @@ public slots:
private
slots:
void directoryChanged(QString path);
+ void updateFinished();
+ void modParseFinished(int token);
signals:
void changed();
+private:
+ void resolveMod(Mod& m);
+
protected:
QFileSystemWatcher *m_watcher;
bool is_watching = false;
+ ModFolderLoadTask::ResultPtr m_update;
+ bool scheduled_update = false;
bool interaction_disabled = false;
QDir m_dir;
+ QMap<QString, int> modsIndex;
+ QMap<int, LocalModParseTask::ResultPtr> activeTickets;
+ int nextResolutionTicket = 0;
QList<Mod> mods;
};
diff --git a/api/logic/minecraft/SimpleModList_test.cpp b/api/logic/minecraft/mod/ModFolderModel_test.cpp
index a100b539..76f16ed5 100644
--- a/api/logic/minecraft/SimpleModList_test.cpp
+++ b/api/logic/minecraft/mod/ModFolderModel_test.cpp
@@ -4,9 +4,9 @@
#include "TestUtil.h"
#include "FileSystem.h"
-#include "minecraft/SimpleModList.h"
+#include "minecraft/mod/ModFolderModel.h"
-class SimpleModListTest : public QObject
+class ModFolderModelTest : public QObject
{
Q_OBJECT
@@ -32,7 +32,7 @@ slots:
{
QString folder = source;
QTemporaryDir tempDir;
- SimpleModList m(tempDir.path());
+ ModFolderModel m(tempDir.path());
m.installMod(folder);
verify(tempDir.path());
}
@@ -41,13 +41,13 @@ slots:
{
QString folder = source + '/';
QTemporaryDir tempDir;
- SimpleModList m(tempDir.path());
+ ModFolderModel m(tempDir.path());
m.installMod(folder);
verify(tempDir.path());
}
}
};
-QTEST_GUILESS_MAIN(SimpleModListTest)
+QTEST_GUILESS_MAIN(ModFolderModelTest)
-#include "SimpleModList_test.moc"
+#include "ModFolderModel_test.moc"
diff --git a/application/pages/instance/ModFolderPage.cpp b/application/pages/instance/ModFolderPage.cpp
index 03bcaa48..97002824 100644
--- a/application/pages/instance/ModFolderPage.cpp
+++ b/application/pages/instance/ModFolderPage.cpp
@@ -25,8 +25,8 @@
#include "MultiMC.h"
#include "dialogs/CustomMessageBox.h"
#include <GuiUtil.h>
-#include "minecraft/SimpleModList.h"
-#include "minecraft/Mod.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "minecraft/mod/Mod.h"
#include "minecraft/VersionFilterData.h"
#include "minecraft/ComponentList.h"
#include <DesktopServices.h>
@@ -53,7 +53,7 @@ public:
protected:
bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override
{
- SimpleModList *model = qobject_cast<SimpleModList *>(sourceModel());
+ ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel());
if(
!model ||
!source_left.isValid() ||
@@ -65,11 +65,11 @@ protected:
// we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and proceed.
- auto column = (SimpleModList::Columns) source_left.column();
+ auto column = (ModFolderModel::Columns) source_left.column();
bool invert = false;
switch(column) {
// GH-2550 - sort by enabled/disabled
- case SimpleModList::ActiveColumn: {
+ case ModFolderModel::ActiveColumn: {
auto dataL = source_left.data(Qt::CheckStateRole).toBool();
auto dataR = source_right.data(Qt::CheckStateRole).toBool();
if(dataL != dataR) {
@@ -79,10 +79,10 @@ protected:
invert = sortOrder() == Qt::DescendingOrder;
}
// GH-2722 - sort mod names in a way that discards "The" prefixes
- case SimpleModList::NameColumn: {
- auto dataL = model->data(model->index(source_left.row(), SimpleModList::NameColumn)).toString();
+ case ModFolderModel::NameColumn: {
+ auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString();
RemoveThePrefix(dataL);
- auto dataR = model->data(model->index(source_right.row(), SimpleModList::NameColumn)).toString();
+ auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString();
RemoveThePrefix(dataR);
auto less = dataL.compare(dataR, sortCaseSensitivity());
@@ -93,9 +93,9 @@ protected:
invert = sortOrder() == Qt::DescendingOrder;
}
// GH-2762 - sort versions by parsing them as versions
- case SimpleModList::VersionColumn: {
- auto dataL = Version(model->data(model->index(source_left.row(), SimpleModList::VersionColumn)).toString());
- auto dataR = Version(model->data(model->index(source_right.row(), SimpleModList::VersionColumn)).toString());
+ case ModFolderModel::VersionColumn: {
+ auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString());
+ auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString());
return invert ? (dataL > dataR) : (dataL < dataR);
}
default: {
@@ -107,7 +107,7 @@ protected:
ModFolderPage::ModFolderPage(
BaseInstance *inst,
- std::shared_ptr<SimpleModList> mods,
+ std::shared_ptr<ModFolderModel> mods,
QString id,
QString iconName,
QString displayName,
@@ -177,7 +177,7 @@ void ModFolderPage::on_filterTextChanged(const QString& newContents)
}
-CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods,
+CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods,
QString id, QString iconName, QString displayName,
QString helpPage, QWidget *parent)
: ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent)
diff --git a/application/pages/instance/ModFolderPage.h b/application/pages/instance/ModFolderPage.h
index a52e1611..a99ab161 100644
--- a/application/pages/instance/ModFolderPage.h
+++ b/application/pages/instance/ModFolderPage.h
@@ -21,7 +21,7 @@
#include "pages/BasePage.h"
#include <MultiMC.h>
-class SimpleModList;
+class ModFolderModel;
namespace Ui
{
class ModFolderPage;
@@ -34,7 +34,7 @@ class ModFolderPage : public QMainWindow, public BasePage
public:
explicit ModFolderPage(
BaseInstance *inst,
- std::shared_ptr<SimpleModList> mods,
+ std::shared_ptr<ModFolderModel> mods,
QString id,
QString iconName,
QString displayName,
@@ -78,7 +78,7 @@ protected:
protected:
Ui::ModFolderPage *ui = nullptr;
- std::shared_ptr<SimpleModList> m_mods;
+ std::shared_ptr<ModFolderModel> m_mods;
QSortFilterProxyModel *m_filterModel = nullptr;
QString m_iconName;
QString m_id;
@@ -108,7 +108,7 @@ slots:
class CoreModFolderPage : public ModFolderPage
{
public:
- explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods, QString id,
+ explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods, QString id,
QString iconName, QString displayName, QString helpPage = "",
QWidget *parent = 0);
virtual ~CoreModFolderPage()
diff --git a/application/pages/instance/VersionPage.cpp b/application/pages/instance/VersionPage.cpp
index e129d03d..2acc13c2 100644
--- a/application/pages/instance/VersionPage.cpp
+++ b/application/pages/instance/VersionPage.cpp
@@ -39,7 +39,7 @@
#include "minecraft/ComponentList.h"
#include "minecraft/auth/MojangAccountList.h"
-#include "minecraft/Mod.h"
+#include "minecraft/mod/Mod.h"
#include "icons/IconList.h"
#include "Exception.h"
#include "Version.h"
diff --git a/application/widgets/MCModInfoFrame.h b/application/widgets/MCModInfoFrame.h
index 512d3bc5..51aa4489 100644
--- a/application/widgets/MCModInfoFrame.h
+++ b/application/widgets/MCModInfoFrame.h
@@ -16,7 +16,7 @@
#pragma once
#include <QFrame>
-#include "minecraft/Mod.h"
+#include "minecraft/mod/Mod.h"
namespace Ui
{