summaryrefslogtreecommitdiffstats
path: root/logic
diff options
context:
space:
mode:
authorForkk <forkk@forkk.net>2014-01-02 13:38:20 -0600
committerForkk <forkk@forkk.net>2014-01-02 13:38:20 -0600
commit17f1864a71b69b9df14d8e06ed48a65e678d09c9 (patch)
tree4d98a2b3493a26017150d6ba8c5ae0419de3de7d /logic
parent4495e20cd7f7f2ab062f3b60f19ac4b79f32c350 (diff)
parentaa5f2c8120cc23de0d57c9f0280512adb9a531b3 (diff)
downloadMultiMC-17f1864a71b69b9df14d8e06ed48a65e678d09c9.tar
MultiMC-17f1864a71b69b9df14d8e06ed48a65e678d09c9.tar.gz
MultiMC-17f1864a71b69b9df14d8e06ed48a65e678d09c9.tar.lz
MultiMC-17f1864a71b69b9df14d8e06ed48a65e678d09c9.tar.xz
MultiMC-17f1864a71b69b9df14d8e06ed48a65e678d09c9.zip
Merge branch 'develop' of github.com:MultiMC/MultiMC5 into feature_news
Conflicts: CMakeLists.txt gui/MainWindow.h
Diffstat (limited to 'logic')
-rw-r--r--logic/BaseInstance.cpp73
-rw-r--r--logic/BaseInstance.h3
-rw-r--r--logic/InstanceFactory.cpp100
-rw-r--r--logic/InstanceFactory.h11
-rw-r--r--logic/JavaChecker.cpp2
-rw-r--r--logic/JavaUtils.cpp4
-rw-r--r--logic/LegacyFTBInstance.cpp16
-rw-r--r--logic/LegacyFTBInstance.h13
-rw-r--r--logic/LegacyInstance.cpp13
-rw-r--r--logic/LegacyUpdate.cpp23
-rw-r--r--logic/LiteLoaderInstaller.cpp102
-rw-r--r--logic/LiteLoaderInstaller.h39
-rw-r--r--logic/Mod.cpp141
-rw-r--r--logic/Mod.h32
-rw-r--r--logic/ModList.cpp225
-rw-r--r--logic/ModList.h18
-rw-r--r--logic/OneSixFTBInstance.cpp120
-rw-r--r--logic/OneSixFTBInstance.h20
-rw-r--r--logic/OneSixInstance.cpp4
-rw-r--r--logic/OneSixLibrary.h6
-rw-r--r--logic/OneSixUpdate.cpp14
-rw-r--r--logic/assets/AssetsMigrateTask.cpp143
-rw-r--r--logic/assets/AssetsMigrateTask.h18
-rw-r--r--logic/assets/AssetsUtils.cpp83
-rw-r--r--logic/assets/AssetsUtils.h2
-rw-r--r--logic/auth/MojangAccount.cpp26
-rw-r--r--logic/auth/MojangAccount.h2
-rw-r--r--logic/auth/YggdrasilTask.cpp36
-rw-r--r--logic/auth/YggdrasilTask.h2
-rw-r--r--logic/auth/flows/AuthenticateTask.cpp4
-rw-r--r--logic/auth/flows/RefreshTask.cpp4
-rw-r--r--logic/auth/flows/ValidateTask.cpp4
-rw-r--r--logic/icons/IconList.cpp351
-rw-r--r--logic/icons/IconList.h (renamed from logic/lists/IconList.h)33
-rw-r--r--logic/icons/MMCIcon.cpp89
-rw-r--r--logic/icons/MMCIcon.h52
-rw-r--r--logic/lists/ForgeVersionList.cpp205
-rw-r--r--logic/lists/ForgeVersionList.h14
-rw-r--r--logic/lists/IconList.cpp271
-rw-r--r--logic/lists/InstanceList.cpp226
-rw-r--r--logic/lists/InstanceList.h16
-rw-r--r--logic/lists/JavaVersionList.cpp13
-rw-r--r--logic/lists/JavaVersionList.h1
-rw-r--r--logic/lists/MinecraftVersionList.cpp2
-rw-r--r--logic/net/ForgeXzDownload.cpp44
-rw-r--r--logic/net/MD5EtagDownload.cpp38
-rw-r--r--logic/net/MD5EtagDownload.h8
-rw-r--r--logic/net/URLConstants.h2
-rw-r--r--logic/tasks/SequentialTask.cpp77
-rw-r--r--logic/tasks/SequentialTask.h32
-rw-r--r--logic/tasks/ThreadTask.cpp41
-rw-r--r--logic/tasks/ThreadTask.h25
-rw-r--r--logic/updater/DownloadUpdateTask.cpp436
-rw-r--r--logic/updater/DownloadUpdateTask.h49
-rw-r--r--logic/updater/UpdateChecker.cpp73
-rw-r--r--logic/updater/UpdateChecker.h11
56 files changed, 2567 insertions, 845 deletions
diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp
index 6f8222b7..ac66a8d5 100644
--- a/logic/BaseInstance.cpp
+++ b/logic/BaseInstance.cpp
@@ -27,6 +27,7 @@
#include "pathutils.h"
#include "lists/MinecraftVersionList.h"
+#include "logic/icons/IconList.h"
BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
SettingsObject *settings_obj, QObject *parent)
@@ -36,10 +37,11 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
d->m_settings = settings_obj;
d->m_rootDir = rootDir;
- settings().registerSetting(new Setting("name", "Unnamed Instance"));
- settings().registerSetting(new Setting("iconKey", "default"));
- settings().registerSetting(new Setting("notes", ""));
- settings().registerSetting(new Setting("lastLaunchTime", 0));
+ settings().registerSetting("name", "Unnamed Instance");
+ settings().registerSetting("iconKey", "default");
+ connect(MMC->icons().get(), SIGNAL(iconUpdated(QString)), SLOT(iconUpdated(QString)));
+ settings().registerSetting("notes", "");
+ settings().registerSetting("lastLaunchTime", 0);
/*
* custom base jar has no default. it is determined in code... see the accessor methods for
@@ -48,54 +50,45 @@ BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
* for instances that DO NOT have the CustomBaseJar setting (legacy instances),
* [.]minecraft/bin/mcbackup.jar is the default base jar
*/
- settings().registerSetting(new Setting("UseCustomBaseJar", true));
- settings().registerSetting(new Setting("CustomBaseJar", ""));
+ settings().registerSetting("UseCustomBaseJar", true);
+ settings().registerSetting("CustomBaseJar", "");
auto globalSettings = MMC->settings();
// Java Settings
- settings().registerSetting(new Setting("OverrideJava", false));
- settings().registerSetting(
- new OverrideSetting("JavaPath", globalSettings->getSetting("JavaPath")));
- settings().registerSetting(
- new OverrideSetting("JvmArgs", globalSettings->getSetting("JvmArgs")));
+ settings().registerSetting("OverrideJava", false);
+ settings().registerOverride(globalSettings->getSetting("JavaPath"));
+ settings().registerOverride(globalSettings->getSetting("JvmArgs"));
// Custom Commands
- settings().registerSetting(new Setting("OverrideCommands", false));
- settings().registerSetting(new OverrideSetting(
- "PreLaunchCommand", globalSettings->getSetting("PreLaunchCommand")));
- settings().registerSetting(
- new OverrideSetting("PostExitCommand", globalSettings->getSetting("PostExitCommand")));
+ settings().registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
+ settings().registerOverride(globalSettings->getSetting("PreLaunchCommand"));
+ settings().registerOverride(globalSettings->getSetting("PostExitCommand"));
// Window Size
- settings().registerSetting(new Setting("OverrideWindow", false));
- settings().registerSetting(
- new OverrideSetting("LaunchMaximized", globalSettings->getSetting("LaunchMaximized")));
- settings().registerSetting(new OverrideSetting(
- "MinecraftWinWidth", globalSettings->getSetting("MinecraftWinWidth")));
- settings().registerSetting(new OverrideSetting(
- "MinecraftWinHeight", globalSettings->getSetting("MinecraftWinHeight")));
+ settings().registerSetting("OverrideWindow", false);
+ settings().registerOverride(globalSettings->getSetting("LaunchMaximized"));
+ settings().registerOverride(globalSettings->getSetting("MinecraftWinWidth"));
+ settings().registerOverride(globalSettings->getSetting("MinecraftWinHeight"));
// Memory
- settings().registerSetting(new Setting("OverrideMemory", false));
- settings().registerSetting(
- new OverrideSetting("MinMemAlloc", globalSettings->getSetting("MinMemAlloc")));
- settings().registerSetting(
- new OverrideSetting("MaxMemAlloc", globalSettings->getSetting("MaxMemAlloc")));
- settings().registerSetting(
- new OverrideSetting("PermGen", globalSettings->getSetting("PermGen")));
-
- // Auto login
- settings().registerSetting(new Setting("OverrideLogin", false));
- settings().registerSetting(
- new OverrideSetting("AutoLogin", globalSettings->getSetting("AutoLogin")));
+ settings().registerSetting("OverrideMemory", false);
+ settings().registerOverride(globalSettings->getSetting("MinMemAlloc"));
+ settings().registerOverride(globalSettings->getSetting("MaxMemAlloc"));
+ settings().registerOverride(globalSettings->getSetting("PermGen"));
// Console
- settings().registerSetting(new Setting("OverrideConsole", false));
- settings().registerSetting(
- new OverrideSetting("ShowConsole", globalSettings->getSetting("ShowConsole")));
- settings().registerSetting(new OverrideSetting(
- "AutoCloseConsole", globalSettings->getSetting("AutoCloseConsole")));
+ settings().registerSetting("OverrideConsole", false);
+ settings().registerOverride(globalSettings->getSetting("ShowConsole"));
+ settings().registerOverride(globalSettings->getSetting("AutoCloseConsole"));
+}
+
+void BaseInstance::iconUpdated(QString key)
+{
+ if(iconKey() == key)
+ {
+ emit propertiesChanged(this);
+ }
}
void BaseInstance::nuke()
diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h
index 5f426676..01d6dc7d 100644
--- a/logic/BaseInstance.h
+++ b/logic/BaseInstance.h
@@ -184,6 +184,9 @@ signals:
*/
void nuked(BaseInstance *inst);
+protected slots:
+ void iconUpdated(QString key);
+
protected:
std::shared_ptr<BaseInstancePrivate> inst_d;
};
diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp
index 66b271d0..1f1a5879 100644
--- a/logic/InstanceFactory.cpp
+++ b/logic/InstanceFactory.cpp
@@ -20,7 +20,9 @@
#include "BaseInstance.h"
#include "LegacyInstance.h"
+#include "LegacyFTBInstance.h"
#include "OneSixInstance.h"
+#include "OneSixFTBInstance.h"
#include "NostalgiaInstance.h"
#include "BaseVersion.h"
#include "MinecraftVersion.h"
@@ -43,7 +45,7 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
{
auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
- m_settings->registerSetting(new Setting("InstanceType", "Legacy"));
+ m_settings->registerSetting("InstanceType", "Legacy");
QString inst_type = m_settings->get("InstanceType").toString();
@@ -60,6 +62,14 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
{
inst = new NostalgiaInstance(instDir, m_settings, this);
}
+ else if (inst_type == "LegacyFTB")
+ {
+ inst = new LegacyFTBInstance(instDir, m_settings, this);
+ }
+ else if (inst_type == "OneSixFTB")
+ {
+ inst = new OneSixFTBInstance(instDir, m_settings, this);
+ }
else
{
return InstanceFactory::UnknownLoadError;
@@ -69,7 +79,8 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst
InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&inst,
BaseVersionPtr version,
- const QString &instDir)
+ const QString &instDir,
+ const InstType type)
{
QDir rootDir(instDir);
@@ -83,34 +94,65 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(BaseInstance *&
return InstanceFactory::NoSuchVersion;
auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
- m_settings->registerSetting(new Setting("InstanceType", "Legacy"));
+ m_settings->registerSetting("InstanceType", "Legacy");
- switch (mcVer->type)
+ if (type == NormalInst)
{
- case MinecraftVersion::Legacy:
- m_settings->set("InstanceType", "Legacy");
- inst = new LegacyInstance(instDir, m_settings, this);
- inst->setIntendedVersionId(version->descriptor());
- inst->setShouldUseCustomBaseJar(false);
- break;
- case MinecraftVersion::OneSix:
- m_settings->set("InstanceType", "OneSix");
- inst = new OneSixInstance(instDir, m_settings, this);
- inst->setIntendedVersionId(version->descriptor());
- inst->setShouldUseCustomBaseJar(false);
- break;
- case MinecraftVersion::Nostalgia:
- m_settings->set("InstanceType", "Nostalgia");
- inst = new NostalgiaInstance(instDir, m_settings, this);
- inst->setIntendedVersionId(version->descriptor());
- inst->setShouldUseCustomBaseJar(false);
- break;
- default:
+ switch (mcVer->type)
+ {
+ case MinecraftVersion::Legacy:
+ m_settings->set("InstanceType", "Legacy");
+ inst = new LegacyInstance(instDir, m_settings, this);
+ inst->setIntendedVersionId(version->descriptor());
+ inst->setShouldUseCustomBaseJar(false);
+ break;
+ case MinecraftVersion::OneSix:
+ m_settings->set("InstanceType", "OneSix");
+ inst = new OneSixInstance(instDir, m_settings, this);
+ inst->setIntendedVersionId(version->descriptor());
+ inst->setShouldUseCustomBaseJar(false);
+ break;
+ case MinecraftVersion::Nostalgia:
+ m_settings->set("InstanceType", "Nostalgia");
+ inst = new NostalgiaInstance(instDir, m_settings, this);
+ inst->setIntendedVersionId(version->descriptor());
+ inst->setShouldUseCustomBaseJar(false);
+ break;
+ default:
+ {
+ delete m_settings;
+ return InstanceFactory::NoSuchVersion;
+ }
+ }
+ }
+ else if (type == FTBInstance)
+ {
+ switch (mcVer->type)
+ {
+ case MinecraftVersion::Legacy:
+ m_settings->set("InstanceType", "LegacyFTB");
+ inst = new LegacyFTBInstance(instDir, m_settings, this);
+ inst->setIntendedVersionId(version->descriptor());
+ inst->setShouldUseCustomBaseJar(false);
+ break;
+ case MinecraftVersion::OneSix:
+ m_settings->set("InstanceType", "OneSixFTB");
+ inst = new OneSixFTBInstance(instDir, m_settings, this);
+ inst->setIntendedVersionId(version->descriptor());
+ inst->setShouldUseCustomBaseJar(false);
+ break;
+ default:
+ {
+ delete m_settings;
+ return InstanceFactory::NoSuchVersion;
+ }
+ }
+ }
+ else
{
delete m_settings;
return InstanceFactory::NoSuchVersion;
}
- }
// FIXME: really, how do you even know?
return InstanceFactory::NoCreateError;
@@ -128,7 +170,17 @@ InstanceFactory::InstCreateError InstanceFactory::copyInstance(BaseInstance *&ne
rootDir.removeRecursively();
return InstanceFactory::CantCreateDir;
}
+ auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
+ m_settings->registerSetting("InstanceType", "Legacy");
+ QString inst_type = m_settings->get("InstanceType").toString();
+
+ if(inst_type == "OneSixFTB")
+ m_settings->set("InstanceType", "OneSix");
+ if(inst_type == "LegacyFTB")
+ m_settings->set("InstanceType", "Legacy");
+
auto error = loadInstance(newInstance, instDir);
+
switch (error)
{
case NoLoadError:
diff --git a/logic/InstanceFactory.h b/logic/InstanceFactory.h
index 01e5af7e..5ff4c7ec 100644
--- a/logic/InstanceFactory.h
+++ b/logic/InstanceFactory.h
@@ -55,18 +55,25 @@ public:
CantCreateDir
};
+ enum InstType
+ {
+ NormalInst,
+ FTBInstance
+ };
+
/*!
* \brief Creates a stub instance
*
* \param inst Pointer to store the created instance in.
- * \param inst Game version to use for the instance
+ * \param version Game version to use for the instance
* \param instDir The new instance's directory.
+ * \param type The type of instance to create
* \return An InstCreateError error code.
* - InstExists if the given instance directory is already an instance.
* - CantCreateDir if the given instance directory cannot be created.
*/
InstCreateError createInstance(BaseInstance *&inst, BaseVersionPtr version,
- const QString &instDir);
+ const QString &instDir, const InstType type = NormalInst);
/*!
* \brief Creates a copy of an existing instance with a new name
diff --git a/logic/JavaChecker.cpp b/logic/JavaChecker.cpp
index 2b94fbb6..113974ff 100644
--- a/logic/JavaChecker.cpp
+++ b/logic/JavaChecker.cpp
@@ -99,6 +99,7 @@ void JavaChecker::error(QProcess::ProcessError err)
if(err == QProcess::FailedToStart)
{
killTimer.stop();
+ checkerJar.remove();
JavaCheckResult result;
{
@@ -116,6 +117,5 @@ void JavaChecker::timeout()
if(process)
{
process->kill();
- process.reset();
}
}
diff --git a/logic/JavaUtils.cpp b/logic/JavaUtils.cpp
index e1b3bc64..cf47df6f 100644
--- a/logic/JavaUtils.cpp
+++ b/logic/JavaUtils.cpp
@@ -177,10 +177,10 @@ QList<QString> JavaUtils::FindJavaPaths()
#elif OSX
QList<QString> JavaUtils::FindJavaPaths()
{
- QLOG_INFO() << "OS X Java detection incomplete - defaulting to \"java\"";
-
QList<QString> javas;
javas.append(this->GetDefaultJava()->path);
+ javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java");
+ javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
return javas;
}
diff --git a/logic/LegacyFTBInstance.cpp b/logic/LegacyFTBInstance.cpp
new file mode 100644
index 00000000..84d5a900
--- /dev/null
+++ b/logic/LegacyFTBInstance.cpp
@@ -0,0 +1,16 @@
+#include "LegacyFTBInstance.h"
+
+LegacyFTBInstance::LegacyFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) :
+ LegacyInstance(rootDir, settings, parent)
+{
+}
+
+QString LegacyFTBInstance::getStatusbarDescription()
+{
+ return "Legacy FTB: " + intendedVersionId();
+}
+
+bool LegacyFTBInstance::menuActionEnabled(QString action_name) const
+{
+ return false;
+}
diff --git a/logic/LegacyFTBInstance.h b/logic/LegacyFTBInstance.h
new file mode 100644
index 00000000..2ae72797
--- /dev/null
+++ b/logic/LegacyFTBInstance.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "LegacyInstance.h"
+
+class LegacyFTBInstance : public LegacyInstance
+{
+ Q_OBJECT
+public:
+ explicit LegacyFTBInstance(const QString &rootDir, SettingsObject *settings,
+ QObject *parent = 0);
+ virtual QString getStatusbarDescription();
+ virtual bool menuActionEnabled(QString action_name) const;
+};
diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp
index fef27bcd..0bc0961e 100644
--- a/logic/LegacyInstance.cpp
+++ b/logic/LegacyInstance.cpp
@@ -27,7 +27,7 @@
#include "logic/MinecraftProcess.h"
#include "logic/LegacyUpdate.h"
-#include "logic/lists/IconList.h"
+#include "logic/icons/IconList.h"
#include "gui/dialogs/LegacyModEditDialog.h"
@@ -37,11 +37,11 @@ LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings,
QObject *parent)
: BaseInstance(new LegacyInstancePrivate(), rootDir, settings, parent)
{
- settings->registerSetting(new Setting("NeedsRebuild", true));
- settings->registerSetting(new Setting("ShouldUpdate", false));
- settings->registerSetting(new Setting("JarVersion", "Unknown"));
- settings->registerSetting(new Setting("LwjglVersion", "2.9.0"));
- settings->registerSetting(new Setting("IntendedJarVersion", ""));
+ settings->registerSetting("NeedsRebuild", true);
+ settings->registerSetting("ShouldUpdate", false);
+ settings->registerSetting("JarVersion", "Unknown");
+ settings->registerSetting("LwjglVersion", "2.9.0");
+ settings->registerSetting("IntendedJarVersion", "");
}
std::shared_ptr<Task> LegacyInstance::doUpdate(bool only_prepare)
@@ -150,6 +150,7 @@ std::shared_ptr<ModList> LegacyInstance::jarModList()
void LegacyInstance::jarModsChanged()
{
+ QLOG_INFO() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt.";
setShouldRebuild(true);
}
diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp
index e71b270e..cb3598a7 100644
--- a/logic/LegacyUpdate.cpp
+++ b/logic/LegacyUpdate.cpp
@@ -76,7 +76,7 @@ void LegacyUpdate::lwjglStart()
return;
}
- setStatus("Downloading new LWJGL.");
+ setStatus(tr("Downloading new LWJGL..."));
auto version = list->getVersion(lwjglVersion);
if (!version)
{
@@ -144,7 +144,7 @@ void LegacyUpdate::lwjglFinished(QNetworkReply *reply)
saveMe.open(QIODevice::WriteOnly);
saveMe.write(m_reply->readAll());
saveMe.close();
- setStatus("Installing new LWJGL...");
+ setStatus(tr("Installing new LWJGL..."));
extractLwjgl();
jarStart();
}
@@ -220,7 +220,7 @@ void LegacyUpdate::extractLwjgl()
// Now if destFileName is still empty, go to the next file.
if (!destFileName.isEmpty())
{
- setStatus("Installing new LWJGL - Extracting " + name);
+ setStatus(tr("Installing new LWJGL - extracting ") + name + "...");
QFile output(destFileName);
output.open(QIODevice::WriteOnly);
output.write(file.readAll()); // FIXME: wste of memory!?
@@ -250,7 +250,7 @@ void LegacyUpdate::jarStart()
return;
}
- setStatus("Checking for jar updates...");
+ setStatus(tr("Checking for jar updates..."));
// Make directories
QDir binDir(inst->binDir());
if (!binDir.exists() && !binDir.mkpath("."))
@@ -260,7 +260,7 @@ void LegacyUpdate::jarStart()
}
// Build a list of URLs that will need to be downloaded.
- setStatus("Downloading new minecraft.jar");
+ setStatus(tr("Downloading new minecraft.jar ..."));
QString version_id = inst->intendedVersionId();
QString localPath = version_id + "/" + version_id + ".jar";
@@ -294,7 +294,7 @@ void LegacyUpdate::jarFailed()
bool LegacyUpdate::MergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
MetainfAction metainf)
{
- setStatus("Installing mods - Adding " + from.fileName());
+ setStatus(tr("Installing mods: Adding ") + from.fileName() + " ...");
QuaZip modZip(from.filePath());
modZip.open(QuaZip::mdUnzip);
@@ -380,7 +380,7 @@ void LegacyUpdate::ModTheJar()
return;
}
- setStatus("Installing mods - backing up minecraft.jar...");
+ setStatus(tr("Installing mods: Backing up minecraft.jar ..."));
if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath()))
{
emitFailed("It seems both the active and base jar are gone. A fresh base jar will "
@@ -405,7 +405,7 @@ void LegacyUpdate::ModTheJar()
}
// TaskStep(); // STEP 1
- setStatus("Installing mods - Opening minecraft.jar");
+ setStatus(tr("Installing mods: Opening minecraft.jar ..."));
QuaZip zipOut(runnableJar.filePath());
if (!zipOut.open(QuaZip::mdCreate))
@@ -419,10 +419,15 @@ void LegacyUpdate::ModTheJar()
QSet<QString> addedFiles;
// Modify the jar
- setStatus("Installing mods - Adding mod files...");
+ setStatus(tr("Installing mods: Adding mod files..."));
for (int i = modList->size() - 1; i >= 0; i--)
{
auto &mod = modList->operator[](i);
+
+ // do not merge disabled mods.
+ if(!mod.enabled())
+ continue;
+
if (mod.type() == Mod::MOD_ZIPFILE)
{
if (!MergeZipFiles(&zipOut, mod.filename(), addedFiles, LegacyUpdate::KeepMetainf))
diff --git a/logic/LiteLoaderInstaller.cpp b/logic/LiteLoaderInstaller.cpp
new file mode 100644
index 00000000..07fffff3
--- /dev/null
+++ b/logic/LiteLoaderInstaller.cpp
@@ -0,0 +1,102 @@
+/* Copyright 2013 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 "LiteLoaderInstaller.h"
+
+#include "OneSixVersion.h"
+#include "OneSixLibrary.h"
+
+QMap<QString, QString> LiteLoaderInstaller::m_launcherWrapperVersionMapping;
+
+LiteLoaderInstaller::LiteLoaderInstaller(const QString &mcVersion) : m_mcVersion(mcVersion)
+{
+ if (m_launcherWrapperVersionMapping.isEmpty())
+ {
+ m_launcherWrapperVersionMapping["1.6.2"] = "1.3";
+ m_launcherWrapperVersionMapping["1.6.4"] = "1.8";
+ //m_launcherWrapperVersionMapping["1.7.2"] = "1.8";
+ //m_launcherWrapperVersionMapping["1.7.4"] = "1.8";
+ }
+}
+
+bool LiteLoaderInstaller::canApply() const
+{
+ return m_launcherWrapperVersionMapping.contains(m_mcVersion);
+}
+
+bool LiteLoaderInstaller::apply(std::shared_ptr<OneSixVersion> to)
+{
+ to->externalUpdateStart();
+
+ applyLaunchwrapper(to);
+ applyLiteLoader(to);
+
+ to->mainClass = "net.minecraft.launchwrapper.Launch";
+ if (!to->minecraftArguments.contains(
+ " --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker"))
+ {
+ to->minecraftArguments.append(
+ " --tweakClass com.mumfrey.liteloader.launch.LiteLoaderTweaker");
+ }
+
+ to->externalUpdateFinish();
+ return to->toOriginalFile();
+}
+
+void LiteLoaderInstaller::applyLaunchwrapper(std::shared_ptr<OneSixVersion> to)
+{
+ const QString intendedVersion = m_launcherWrapperVersionMapping[m_mcVersion];
+
+ QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries);
+ while (it.hasNext())
+ {
+ it.next();
+ if (it.value()->rawName().startsWith("net.minecraft:launchwrapper:"))
+ {
+ if (it.value()->version() >= intendedVersion)
+ {
+ return;
+ }
+ else
+ {
+ it.remove();
+ }
+ }
+ }
+
+ std::shared_ptr<OneSixLibrary> lib(new OneSixLibrary(
+ "net.minecraft:launchwrapper:" + m_launcherWrapperVersionMapping[m_mcVersion]));
+ lib->finalize();
+ to->libraries.prepend(lib);
+}
+
+void LiteLoaderInstaller::applyLiteLoader(std::shared_ptr<OneSixVersion> to)
+{
+ QMutableListIterator<std::shared_ptr<OneSixLibrary>> it(to->libraries);
+ while (it.hasNext())
+ {
+ it.next();
+ if (it.value()->rawName().startsWith("com.mumfrey:liteloader:"))
+ {
+ it.remove();
+ }
+ }
+
+ std::shared_ptr<OneSixLibrary> lib(
+ new OneSixLibrary("com.mumfrey:liteloader:" + m_mcVersion));
+ lib->setBaseUrl("http://dl.liteloader.com/versions/");
+ lib->finalize();
+ to->libraries.prepend(lib);
+}
diff --git a/logic/LiteLoaderInstaller.h b/logic/LiteLoaderInstaller.h
new file mode 100644
index 00000000..44b306d6
--- /dev/null
+++ b/logic/LiteLoaderInstaller.h
@@ -0,0 +1,39 @@
+/* Copyright 2013 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.
+ */
+
+#pragma once
+#include <QString>
+#include <QMap>
+#include <memory>
+
+class OneSixVersion;
+
+class LiteLoaderInstaller
+{
+public:
+ LiteLoaderInstaller(const QString &mcVersion);
+
+ bool canApply() const;
+
+ bool apply(std::shared_ptr<OneSixVersion> to);
+
+private:
+ QString m_mcVersion;
+
+ void applyLaunchwrapper(std::shared_ptr<OneSixVersion> to);
+ void applyLiteLoader(std::shared_ptr<OneSixVersion> to);
+
+ static QMap<QString, QString> m_launcherWrapperVersionMapping;
+};
diff --git a/logic/Mod.cpp b/logic/Mod.cpp
index cff9467e..6732446d 100644
--- a/logic/Mod.cpp
+++ b/logic/Mod.cpp
@@ -35,20 +35,45 @@ Mod::Mod(const QFileInfo &file)
void Mod::repath(const QFileInfo &file)
{
m_file = file;
- m_name = file.completeBaseName();
- m_id = file.fileName();
+ 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())
{
- QString ext = m_file.suffix().toLower();
- if (ext == "zip" || ext == "jar")
+ 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());
@@ -59,7 +84,7 @@ void Mod::repath(const QFileInfo &file)
if (zip.setCurrentFile("mcmod.info"))
{
- if(!file.open(QIODevice::ReadOnly))
+ if (!file.open(QIODevice::ReadOnly))
{
zip.close();
return;
@@ -100,6 +125,27 @@ void Mod::repath(const QFileInfo &file)
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;
+ }
+
+ ReadLiteModInfo(file.readAll());
+ file.close();
+ }
+ zip.close();
+ }
}
// NEW format
@@ -114,7 +160,7 @@ void Mod::ReadMCModInfo(QByteArray contents)
if (!arr.at(0).isObject())
return;
auto firstObj = arr.at(0).toObject();
- m_id = firstObj.value("modid").toString();
+ m_mod_id = firstObj.value("modid").toString();
m_name = firstObj.value("name").toString();
m_version = firstObj.value("version").toString();
m_homeurl = firstObj.value("url").toString();
@@ -132,8 +178,7 @@ void Mod::ReadMCModInfo(QByteArray contents)
}
m_credits = firstObj.value("credits").toString();
return;
- }
- ;
+ };
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
// this is the very old format that had just the array
@@ -163,7 +208,7 @@ void Mod::ReadForgeInfo(QByteArray contents)
{
// Read the data
m_name = "Minecraft Forge";
- m_id = "Forge";
+ m_mod_id = "Forge";
m_homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini;
if (!ini.loadFile(contents))
@@ -177,15 +222,40 @@ void Mod::ReadForgeInfo(QByteArray contents)
m_version = major + "." + minor + "." + revision + "." + build;
}
+void Mod::ReadLiteModInfo(QByteArray contents)
+{
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ auto object = jsonDoc.object();
+ if(object.contains("name"))
+ {
+ m_mod_id = m_name = object.value("name").toString();
+ }
+ if(object.contains("version"))
+ {
+ m_version=object.value("version").toString("");
+ }
+ else
+ {
+ m_version=object.value("revision").toString("");
+ }
+ m_mcversion = object.value("mcversion").toString();
+ m_authors = object.value("author").toString();
+ m_description = object.value("description").toString();
+ m_homeurl = object.value("url").toString();
+}
+
bool Mod::replace(Mod &with)
{
if (!destroy())
return false;
bool success = false;
auto t = with.type();
+
if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE)
{
- success = QFile::copy(with.m_file.filePath(), m_file.path());
+ QLOG_DEBUG() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath();
+ success = QFile::copy(with.m_file.filePath(), m_file.filePath());
}
if (t == MOD_FOLDER)
{
@@ -193,11 +263,17 @@ bool Mod::replace(Mod &with)
}
if (success)
{
- m_id = with.m_id;
- m_mcversion = with.m_mcversion;
- m_type = with.m_type;
m_name = with.m_name;
+ m_mmc_id = with.m_mmc_id;
+ m_mod_id = with.m_mod_id;
m_version = with.m_version;
+ m_mcversion = with.m_mcversion;
+ m_description = with.m_description;
+ m_authors = with.m_authors;
+ m_credits = with.m_credits;
+ m_homeurl = with.m_homeurl;
+ m_type = with.m_type;
+ m_file.refresh();
}
return success;
}
@@ -232,6 +308,7 @@ QString Mod::version() const
switch (type())
{
case MOD_ZIPFILE:
+ case MOD_LITEMOD:
return m_version;
case MOD_FOLDER:
return "Folder";
@@ -241,3 +318,41 @@ QString Mod::version() const
return "VOID";
}
}
+
+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::operator==(const Mod &other) const
+{
+ return mmc_id() == other.mmc_id();
+}
+bool Mod::strongCompare(const Mod &other) const
+{
+ return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type();
+}
diff --git a/logic/Mod.h b/logic/Mod.h
index ca362a9d..2eb2b97a 100644
--- a/logic/Mod.h
+++ b/logic/Mod.h
@@ -25,6 +25,7 @@ public:
MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files.
MOD_SINGLEFILE, //!< The mod is a single file (not a zip file).
MOD_FOLDER, //!< The mod is in a folder on the filesystem.
+ MOD_LITEMOD, //!< The mod is a litemod
};
Mod(const QFileInfo &file);
@@ -33,9 +34,13 @@ public:
{
return m_file;
}
- QString id() const
+ QString mmc_id() const
{
- return m_id;
+ return m_mmc_id;
+ }
+ QString mod_id() const
+ {
+ return m_mod_id;
}
ModType type() const
{
@@ -77,6 +82,13 @@ public:
return m_credits;
}
+ bool enabled() const
+ {
+ return m_enabled;
+ }
+
+ bool enable(bool value);
+
// delete all the files of this mod
bool destroy();
// replace this mod with a copy of the other
@@ -85,19 +97,13 @@ public:
void repath(const QFileInfo &file);
// WEAK compare operator - used for replacing mods
- bool operator==(const Mod &other) const
- {
- return filename() == other.filename();
- }
- bool strongCompare(const Mod &other) const
- {
- return filename() == other.filename() && id() == other.id() &&
- version() == other.version() && type() == other.type();
- }
+ bool operator==(const Mod &other) const;
+ bool strongCompare(const Mod &other) const;
private:
void ReadMCModInfo(QByteArray contents);
void ReadForgeInfo(QByteArray contents);
+ void ReadLiteModInfo(QByteArray contents);
protected:
@@ -108,7 +114,9 @@ protected:
*/
QFileInfo m_file;
- QString m_id;
+ QString m_mmc_id;
+ QString m_mod_id;
+ bool m_enabled = true;
QString m_name;
QString m_version;
QString m_mcversion;
diff --git a/logic/ModList.cpp b/logic/ModList.cpp
index d5235fe9..fd41bcf7 100644
--- a/logic/ModList.cpp
+++ b/logic/ModList.cpp
@@ -19,6 +19,7 @@
#include <QMimeData>
#include <QUrl>
#include <QUuid>
+#include <QString>
#include <QFileSystemWatcher>
#include "logger/QsLog.h"
@@ -27,7 +28,7 @@ ModList::ModList(const QString &dir, const QString &list_file)
{
m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
QDir::NoSymLinks);
- m_dir.setSorting(QDir::Name);
+ m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
m_list_id = QUuid::createUuid().toString();
m_watcher = new QFileSystemWatcher(this);
is_watching = false;
@@ -66,52 +67,89 @@ bool ModList::update()
if (!isValid())
return false;
+ QList<Mod> orderedMods;
QList<Mod> newMods;
m_dir.refresh();
auto folderContents = m_dir.entryInfoList();
- bool orderWasInvalid = false;
+ bool orderOrStateChanged = false;
// first, process the ordered items (if any)
- QStringList listOrder = readListFile();
+ OrderList listOrder = readListFile();
for (auto item : listOrder)
{
- QFileInfo info(m_dir.filePath(item));
- int idx = folderContents.indexOf(info);
+ QFileInfo infoEnabled(m_dir.filePath(item.id));
+ QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled"));
+ int idxEnabled = folderContents.indexOf(infoEnabled);
+ int idxDisabled = folderContents.indexOf(infoDisabled);
+ bool isEnabled;
+ // if both enabled and disabled versions are present, it's a special case...
+ if (idxEnabled >= 0 && idxDisabled >= 0)
+ {
+ // we only process the one we actually have in the order file.
+ // and exactly as we have it.
+ // THIS IS A CORNER CASE
+ isEnabled = item.enabled;
+ }
+ else
+ {
+ // only one is present.
+ // we pick the one that we found.
+ // we assume the mod was enabled/disabled by external means
+ isEnabled = idxEnabled >= 0;
+ }
+ int idx = isEnabled ? idxEnabled : idxDisabled;
+ QFileInfo & info = isEnabled ? infoEnabled : infoDisabled;
// if the file from the index file exists
if (idx != -1)
{
// remove from the actual folder contents list
folderContents.takeAt(idx);
// append the new mod
- newMods.append(Mod(info));
+ orderedMods.append(Mod(info));
+ if (isEnabled != item.enabled)
+ orderOrStateChanged = true;
}
else
{
- orderWasInvalid = true;
+ orderOrStateChanged = true;
}
}
- for (auto entry : folderContents)
+ // if there are any untracked files...
+ if (folderContents.size())
{
- newMods.append(Mod(entry));
+ // the order surely changed!
+ for (auto entry : folderContents)
+ {
+ newMods.append(Mod(entry));
+ }
+ std::sort(newMods.begin(), newMods.end(), [](const Mod & left, const Mod & right)
+ { return left.name().localeAwareCompare(right.name()) <= 0; });
+ orderedMods.append(newMods);
+ orderOrStateChanged = true;
}
- if (mods.size() != newMods.size())
+ // otherwise, if we were already tracking some mods
+ else if (mods.size())
{
- orderWasInvalid = true;
- }
- else
- for (int i = 0; i < mods.size(); i++)
- {
- if (!mods[i].strongCompare(newMods[i]))
+ // if the number doesn't match, order changed.
+ if (mods.size() != orderedMods.size())
+ orderOrStateChanged = true;
+ // if it does match, compare the mods themselves
+ else
+ for (int i = 0; i < mods.size(); i++)
{
- orderWasInvalid = true;
- break;
+ if (!mods[i].strongCompare(orderedMods[i]))
+ {
+ orderOrStateChanged = true;
+ break;
+ }
}
- }
+ }
beginResetModel();
- mods.swap(newMods);
+ mods.swap(orderedMods);
endResetModel();
- if (orderWasInvalid)
+ if (orderOrStateChanged && !m_list_file.isEmpty())
{
+ QLOG_INFO() << "Mod list " << m_list_file << " changed!";
saveListFile();
emit changed();
}
@@ -123,17 +161,19 @@ void ModList::directoryChanged(QString path)
update();
}
-QStringList ModList::readListFile()
+ModList::OrderList ModList::readListFile()
{
- QStringList stringList;
+ OrderList itemList;
if (m_list_file.isNull() || m_list_file.isEmpty())
- return stringList;
+ return itemList;
QFile textFile(m_list_file);
if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
- return QStringList();
+ return OrderList();
- QTextStream textStream(&textFile);
+ QTextStream textStream;
+ textStream.setAutoDetectUnicode(true);
+ textStream.setDevice(&textFile);
while (true)
{
QString line = textStream.readLine();
@@ -141,11 +181,18 @@ QStringList ModList::readListFile()
break;
else
{
- stringList.append(line);
+ OrderItem it;
+ it.enabled = !line.endsWith(".disabled");
+ if (!it.enabled)
+ {
+ line.chop(9);
+ }
+ it.id = line;
+ itemList.append(it);
}
}
textFile.close();
- return stringList;
+ return itemList;
}
bool ModList::saveListFile()
@@ -155,12 +202,16 @@ bool ModList::saveListFile()
QFile textFile(m_list_file);
if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
return false;
- QTextStream textStream(&textFile);
+ QTextStream textStream;
+ textStream.setGenerateByteOrderMark(true);
+ textStream.setCodec("UTF-8");
+ textStream.setDevice(&textFile);
for (auto mod : mods)
{
- auto pathname = mod.filename();
- QString filename = pathname.fileName();
- textStream << filename << endl;
+ textStream << mod.mmc_id();
+ if (!mod.enabled())
+ textStream << ".disabled";
+ textStream << endl;
}
textFile.close();
return false;
@@ -185,6 +236,9 @@ bool ModList::installMod(const QFileInfo &filename, int index)
int idx = mods.indexOf(m);
if (idx != -1)
{
+ int idx2 = mods.indexOf(m,idx+1);
+ if(idx2 != -1)
+ return false;
if (mods[idx].replace(m))
{
@@ -201,7 +255,7 @@ bool ModList::installMod(const QFileInfo &filename, int index)
auto type = m.type();
if (type == Mod::MOD_UNKNOWN)
return false;
- if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE)
+ if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
{
QString newpath = PathCombine(m_dir.path(), filename.fileName());
if (!QFile::copy(filename.filePath(), newpath))
@@ -327,7 +381,7 @@ bool ModList::moveModsDown(int first, int last)
int ModList::columnCount(const QModelIndex &parent) const
{
- return 2;
+ return 3;
}
QVariant ModList::data(const QModelIndex &index, int role) const
@@ -341,43 +395,96 @@ QVariant ModList::data(const QModelIndex &index, int role) const
if (row < 0 || row >= mods.size())
return QVariant();
- if (role != Qt::DisplayRole)
- return QVariant();
-
- switch (column)
+ switch (role)
{
- case 0:
- return mods[row].name();
- case 1:
- return mods[row].version();
- case 2:
- return mods[row].mcversion();
+ case Qt::DisplayRole:
+ switch (index.column())
+ {
+ case NameColumn:
+ return mods[row].name();
+ case VersionColumn:
+ return mods[row].version();
+
+ default:
+ return QVariant();
+ }
+
+ case Qt::ToolTipRole:
+ return mods[row].mmc_id();
+
+ case Qt::CheckStateRole:
+ switch (index.column())
+ {
+ case ActiveColumn:
+ return mods[row].enabled();
+ default:
+ return QVariant();
+ }
default:
return QVariant();
}
}
+bool ModList::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
+ {
+ return false;
+ }
+
+ if (role == Qt::CheckStateRole)
+ {
+ auto &mod = mods[index.row()];
+ if (mod.enable(!mod.enabled()))
+ {
+ emit dataChanged(index, index);
+ return true;
+ }
+ }
+ return false;
+}
+
QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const
{
- if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
- return QVariant();
- switch (section)
+ switch (role)
{
- case 0:
- return QString("Name");
- case 1:
- return QString("Version");
- case 2:
- return QString("Minecraft");
+ case Qt::DisplayRole:
+ switch (section)
+ {
+ case ActiveColumn:
+ return QString();
+ case NameColumn:
+ return QString("Name");
+ case VersionColumn:
+ return QString("Version");
+ default:
+ return QVariant();
+ }
+
+ case Qt::ToolTipRole:
+ switch (section)
+ {
+ case ActiveColumn:
+ return "Is the mod enabled?";
+ case NameColumn:
+ return "The name of the mod.";
+ case VersionColumn:
+ return "The version of the mod.";
+ default:
+ return QVariant();
+ }
+ default:
+ return QVariant();
}
- return QString();
+ return QVariant();
}
Qt::ItemFlags ModList::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
- return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
+ return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
+ defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
@@ -456,6 +563,14 @@ bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row
QString filename = url.toLocalFile();
installMod(filename, row);
QLOG_INFO() << "installing: " << filename;
+ // if there is no ordering, re-sort the list
+ if (m_list_file.isEmpty())
+ {
+ beginResetModel();
+ std::sort(mods.begin(), mods.end(), [](const Mod & left, const Mod & right)
+ { return left.name().localeAwareCompare(right.name()) <= 0; });
+ endResetModel();
+ }
}
if (was_watching)
startWatching();
diff --git a/logic/ModList.h b/logic/ModList.h
index 803a5429..0d6507fb 100644
--- a/logic/ModList.h
+++ b/logic/ModList.h
@@ -34,9 +34,18 @@ class ModList : public QAbstractListModel
{
Q_OBJECT
public:
+ enum Columns
+ {
+ ActiveColumn = 0,
+ NameColumn,
+ VersionColumn
+ };
ModList(const QString &dir, const QString &list_file = QString());
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual bool setData(const QModelIndex &index, const QVariant &value,
+ int role = Qt::EditRole);
+
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
{
return size();
@@ -59,7 +68,6 @@ public:
{
return mods[index];
}
- ;
/// Reloads the mod list and returns true if the list changed.
virtual bool update();
@@ -119,7 +127,13 @@ public:
}
private:
- QStringList readListFile();
+ struct OrderItem
+ {
+ QString id;
+ bool enabled = false;
+ };
+ typedef QList<OrderItem> OrderList;
+ OrderList readListFile();
bool saveListFile();
private
slots:
diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp
new file mode 100644
index 00000000..4bb5cf42
--- /dev/null
+++ b/logic/OneSixFTBInstance.cpp
@@ -0,0 +1,120 @@
+#include "OneSixFTBInstance.h"
+
+#include "OneSixVersion.h"
+#include "OneSixLibrary.h"
+#include "tasks/SequentialTask.h"
+#include "ForgeInstaller.h"
+#include "lists/ForgeVersionList.h"
+#include "MultiMC.h"
+
+class OneSixFTBInstanceForge : public Task
+{
+ Q_OBJECT
+public:
+ explicit OneSixFTBInstanceForge(const QString &version, OneSixFTBInstance *inst, QObject *parent = 0) :
+ Task(parent), instance(inst), version("Forge " + version)
+ {
+ }
+
+ void executeTask()
+ {
+ for (int i = 0; i < MMC->forgelist()->count(); ++i)
+ {
+ if (MMC->forgelist()->at(i)->name() == version)
+ {
+ forgeVersion = std::dynamic_pointer_cast<ForgeVersion>(MMC->forgelist()->at(i));
+ break;
+ }
+ }
+ if (!forgeVersion)
+ {
+ emitFailed(QString("Couldn't find forge version ") + version );
+ return;
+ }
+ entry = MMC->metacache()->resolveEntry("minecraftforge", forgeVersion->filename);
+ if (entry->stale)
+ {
+ setStatus(tr("Downloading Forge..."));
+ fjob = new NetJob("Forge download");
+ fjob->addNetAction(CacheDownload::make(forgeVersion->installer_url, entry));
+ connect(fjob, &NetJob::failed, [this](){emitFailed(m_failReason);});
+ connect(fjob, &NetJob::succeeded, this, &OneSixFTBInstanceForge::installForge);
+ connect(fjob, &NetJob::progress, [this](qint64 c, qint64 total){ setProgress(100 * c / total); });
+ fjob->start();
+ }
+ else
+ {
+ installForge();
+ }
+ }
+
+private
+slots:
+ void installForge()
+ {
+ setStatus(tr("Installing Forge..."));
+ QString forgePath = entry->getFullPath();
+ ForgeInstaller forge(forgePath, forgeVersion->universal_url);
+ if (!instance->reloadFullVersion())
+ {
+ emitFailed(tr("Couldn't load the version config"));
+ return;
+ }
+ instance->revertCustomVersion();
+ instance->customizeVersion();
+ auto version = instance->getFullVersion();
+ if (!forge.apply(version))
+ {
+ emitFailed(tr("Couldn't install Forge"));
+ return;
+ }
+ emitSucceeded();
+ }
+
+private:
+ OneSixFTBInstance *instance;
+ QString version;
+ ForgeVersionPtr forgeVersion;
+ MetaEntryPtr entry;
+ NetJob *fjob;
+};
+
+OneSixFTBInstance::OneSixFTBInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) :
+ OneSixInstance(rootDir, settings, parent)
+{
+ QFile f(QDir(minecraftRoot()).absoluteFilePath("pack.json"));
+ if (f.open(QFile::ReadOnly))
+ {
+ QString data = QString::fromUtf8(f.readAll());
+ QRegularExpressionMatch match = QRegularExpression("net.minecraftforge:minecraftforge:[\\.\\d]*").match(data);
+ m_forge.reset(new OneSixLibrary(match.captured()));
+ m_forge->finalize();
+ }
+}
+
+QString OneSixFTBInstance::getStatusbarDescription()
+{
+ return "OneSix FTB: " + intendedVersionId();
+}
+bool OneSixFTBInstance::menuActionEnabled(QString action_name) const
+{
+ return false;
+}
+
+std::shared_ptr<Task> OneSixFTBInstance::doUpdate(bool only_prepare)
+{
+ std::shared_ptr<SequentialTask> task;
+ task.reset(new SequentialTask(this));
+ if (!MMC->forgelist()->isLoaded())
+ {
+ task->addTask(std::shared_ptr<Task>(MMC->forgelist()->getLoadTask()));
+ }
+ task->addTask(OneSixInstance::doUpdate(only_prepare));
+ task->addTask(std::shared_ptr<Task>(new OneSixFTBInstanceForge(m_forge->version(), this, this)));
+ //FIXME: yes. this may appear dumb. but the previous step can change the list, so we do it all again.
+ //TODO: Add a graph task. Construct graphs of tasks so we may capture the logic properly.
+ task->addTask(OneSixInstance::doUpdate(only_prepare));
+ return task;
+}
+
+#include "OneSixFTBInstance.moc"
diff --git a/logic/OneSixFTBInstance.h b/logic/OneSixFTBInstance.h
new file mode 100644
index 00000000..7600090c
--- /dev/null
+++ b/logic/OneSixFTBInstance.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "OneSixInstance.h"
+
+class OneSixLibrary;
+
+class OneSixFTBInstance : public OneSixInstance
+{
+ Q_OBJECT
+public:
+ explicit OneSixFTBInstance(const QString &rootDir, SettingsObject *settings,
+ QObject *parent = 0);
+ virtual QString getStatusbarDescription();
+ virtual bool menuActionEnabled(QString action_name) const;
+
+ virtual std::shared_ptr<Task> doUpdate(bool only_prepare) override;
+
+private:
+ std::shared_ptr<OneSixLibrary> m_forge;
+};
diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp
index fd41b9e5..2392c683 100644
--- a/logic/OneSixInstance.cpp
+++ b/logic/OneSixInstance.cpp
@@ -33,8 +33,8 @@ OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_o
: BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent)
{
I_D(OneSixInstance);
- d->m_settings->registerSetting(new Setting("IntendedVersion", ""));
- d->m_settings->registerSetting(new Setting("ShouldUpdate", false));
+ d->m_settings->registerSetting("IntendedVersion", "");
+ d->m_settings->registerSetting("ShouldUpdate", false);
reloadFullVersion();
}
diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h
index 5cb867c2..3f0bc83d 100644
--- a/logic/OneSixLibrary.h
+++ b/logic/OneSixLibrary.h
@@ -68,6 +68,12 @@ public:
m_name = name;
}
+ /// Returns the raw name field
+ QString rawName() const
+ {
+ return m_name;
+ }
+
QJsonObject toJson();
/**
diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp
index 696eeff0..4d93477a 100644
--- a/logic/OneSixUpdate.cpp
+++ b/logic/OneSixUpdate.cpp
@@ -57,7 +57,7 @@ void OneSixUpdate::executeTask()
/*
* FIXME: in offline mode, do not proceed!
*/
- setStatus("Testing the Java installation.");
+ setStatus(tr("Testing the Java installation..."));
QString java_path = m_inst->settings().get("JavaPath").toString();
checker.reset(new JavaChecker());
@@ -89,7 +89,7 @@ void OneSixUpdate::executeTask()
void OneSixUpdate::checkJavaOnline()
{
- setStatus("Testing the Java installation.");
+ setStatus(tr("Testing the Java installation..."));
QString java_path = m_inst->settings().get("JavaPath").toString();
checker.reset(new JavaChecker());
@@ -128,7 +128,7 @@ void OneSixUpdate::checkFinishedOffline(JavaCheckResult result)
void OneSixUpdate::versionFileStart()
{
QLOG_INFO() << m_inst->name() << ": getting version file.";
- setStatus("Getting the version files from Mojang.");
+ setStatus(tr("Getting the version files from Mojang..."));
QString urlstr = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json";
auto job = new NetJob("Version index");
@@ -196,7 +196,7 @@ void OneSixUpdate::versionFileFailed()
void OneSixUpdate::assetIndexStart()
{
- setStatus("Updating asset index.");
+ setStatus(tr("Updating assets index..."));
OneSixInstance *inst = (OneSixInstance *)m_inst;
std::shared_ptr<OneSixVersion> version = inst->getFullVersion();
QString assetName = version->assets;
@@ -247,7 +247,7 @@ void OneSixUpdate::assetIndexFinished()
}
if(dls.size())
{
- setStatus("Getting the assets files from Mojang...");
+ setStatus(tr("Getting the assets files from Mojang..."));
auto job = new NetJob("Assets for " + inst->name());
for(auto dl: dls)
job->addNetAction(dl);
@@ -281,7 +281,7 @@ void OneSixUpdate::assetsFailed()
void OneSixUpdate::jarlibStart()
{
- setStatus("Getting the library files from Mojang.");
+ setStatus(tr("Getting the library files from Mojang..."));
QLOG_INFO() << m_inst->name() << ": downloading libraries";
OneSixInstance *inst = (OneSixInstance *)m_inst;
bool successful = inst->reloadFullVersion();
@@ -369,7 +369,7 @@ void OneSixUpdate::jarlibFailed()
void OneSixUpdate::prepareForLaunch()
{
- setStatus("Preparing for launch.");
+ setStatus(tr("Preparing for launch..."));
QLOG_INFO() << m_inst->name() << ": preparing for launch";
auto onesix_inst = (OneSixInstance *)m_inst;
diff --git a/logic/assets/AssetsMigrateTask.cpp b/logic/assets/AssetsMigrateTask.cpp
new file mode 100644
index 00000000..7c1f5204
--- /dev/null
+++ b/logic/assets/AssetsMigrateTask.cpp
@@ -0,0 +1,143 @@
+#include "AssetsMigrateTask.h"
+#include "MultiMC.h"
+#include "logger/QsLog.h"
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QDirIterator>
+#include <QCryptographicHash>
+#include "gui/dialogs/CustomMessageBox.h"
+#include <QDesktopServices>
+
+AssetsMigrateTask::AssetsMigrateTask(int expected, QObject *parent)
+ : Task(parent)
+{
+ this->m_expected = expected;
+}
+
+void AssetsMigrateTask::executeTask()
+{
+ this->setStatus(tr("Migrating legacy assets..."));
+ this->setProgress(0);
+
+ QDir assets_dir("assets");
+ if (!assets_dir.exists())
+ {
+ emitFailed("Assets directory didn't exist");
+ return;
+ }
+ assets_dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
+ int base_length = assets_dir.path().length();
+
+ QList<QString> blacklist = {"indexes", "objects", "virtual"};
+
+ if (!assets_dir.exists("objects"))
+ assets_dir.mkdir("objects");
+ QDir objects_dir("assets/objects");
+
+ QDirIterator iterator(assets_dir, QDirIterator::Subdirectories);
+ int successes = 0;
+ int failures = 0;
+ while (iterator.hasNext())
+ {
+ QString currentDir = iterator.next();
+ currentDir = currentDir.remove(0, base_length + 1);
+
+ bool ignore = false;
+ for (QString blacklisted : blacklist)
+ {
+ if (currentDir.startsWith(blacklisted))
+ ignore = true;
+ }
+
+ if (!iterator.fileInfo().isDir() && !ignore)
+ {
+ QString filename = iterator.filePath();
+
+ QFile input(filename);
+ input.open(QIODevice::ReadOnly | QIODevice::WriteOnly);
+ QString sha1sum =
+ QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1)
+ .toHex()
+ .constData();
+
+ QString object_name = filename.remove(0, base_length + 1);
+ QLOG_DEBUG() << "Processing" << object_name << ":" << sha1sum << input.size();
+
+ QString object_tlk = sha1sum.left(2);
+ QString object_tlk_dir = objects_dir.path() + "/" + object_tlk;
+
+ QDir tlk_dir(object_tlk_dir);
+ if (!tlk_dir.exists())
+ objects_dir.mkdir(object_tlk);
+
+ QString new_filename = tlk_dir.path() + "/" + sha1sum;
+ QFile new_object(new_filename);
+ if (!new_object.exists())
+ {
+ bool rename_success = input.rename(new_filename);
+ QLOG_DEBUG() << " Doesn't exist, copying to" << new_filename << ":"
+ << QString::number(rename_success);
+ if (rename_success)
+ successes++;
+ else
+ failures++;
+ }
+ else
+ {
+ input.remove();
+ QLOG_DEBUG() << " Already exists, deleting original and not copying.";
+ }
+
+ this->setProgress(100 * ((successes + failures) / (float) this->m_expected));
+ }
+ }
+
+ if (successes + failures == 0)
+ {
+ this->setProgress(100);
+ QLOG_DEBUG() << "No legacy assets needed importing.";
+ }
+ else
+ {
+ QLOG_DEBUG() << "Finished copying legacy assets:" << successes << "successes and"
+ << failures << "failures.";
+
+ this->setStatus("Cleaning up legacy assets...");
+ this->setProgress(100);
+
+ QDirIterator cleanup_iterator(assets_dir);
+
+ while (cleanup_iterator.hasNext())
+ {
+ QString currentDir = cleanup_iterator.next();
+ currentDir = currentDir.remove(0, base_length + 1);
+
+ bool ignore = false;
+ for (QString blacklisted : blacklist)
+ {
+ if (currentDir.startsWith(blacklisted))
+ ignore = true;
+ }
+
+ if (cleanup_iterator.fileInfo().isDir() && !ignore)
+ {
+ QString path = cleanup_iterator.filePath();
+ QDir folder(path);
+
+ QLOG_DEBUG() << "Cleaning up legacy assets folder:" << path;
+
+ folder.removeRecursively();
+ }
+ }
+ }
+
+ if(failures > 0)
+ {
+ emitFailed(QString("Failed to migrate %1 legacy assets").arg(failures));
+ }
+ else
+ {
+ emitSucceeded();
+ }
+}
+
diff --git a/logic/assets/AssetsMigrateTask.h b/logic/assets/AssetsMigrateTask.h
new file mode 100644
index 00000000..d8d58c97
--- /dev/null
+++ b/logic/assets/AssetsMigrateTask.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "logic/tasks/Task.h"
+#include <QMessageBox>
+#include <QNetworkReply>
+#include <memory>
+
+class AssetsMigrateTask : public Task
+{
+ Q_OBJECT
+public:
+ explicit AssetsMigrateTask(int expected, QObject* parent=0);
+
+protected:
+ virtual void executeTask();
+
+private:
+ int m_expected;
+};
diff --git a/logic/assets/AssetsUtils.cpp b/logic/assets/AssetsUtils.cpp
index 11d928cf..bca7773d 100644
--- a/logic/assets/AssetsUtils.cpp
+++ b/logic/assets/AssetsUtils.cpp
@@ -25,23 +25,18 @@
namespace AssetsUtils
{
-void migrateOldAssets()
+int findLegacyAssets()
{
QDir assets_dir("assets");
if (!assets_dir.exists())
- return;
+ return 0;
assets_dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
int base_length = assets_dir.path().length();
QList<QString> blacklist = {"indexes", "objects", "virtual"};
- if (!assets_dir.exists("objects"))
- assets_dir.mkdir("objects");
- QDir objects_dir("assets/objects");
-
QDirIterator iterator(assets_dir, QDirIterator::Subdirectories);
- int successes = 0;
- int failures = 0;
+ int found = 0;
while (iterator.hasNext())
{
QString currentDir = iterator.next();
@@ -56,79 +51,11 @@ void migrateOldAssets()
if (!iterator.fileInfo().isDir() && !ignore)
{
- QString filename = iterator.filePath();
-
- QFile input(filename);
- input.open(QIODevice::ReadOnly | QIODevice::WriteOnly);
- QString sha1sum =
- QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1)
- .toHex()
- .constData();
-
- QString object_name = filename.remove(0, base_length + 1);
- QLOG_DEBUG() << "Processing" << object_name << ":" << sha1sum << input.size();
-
- QString object_tlk = sha1sum.left(2);
- QString object_tlk_dir = objects_dir.path() + "/" + object_tlk;
-
- QDir tlk_dir(object_tlk_dir);
- if (!tlk_dir.exists())
- objects_dir.mkdir(object_tlk);
-
- QString new_filename = tlk_dir.path() + "/" + sha1sum;
- QFile new_object(new_filename);
- if (!new_object.exists())
- {
- bool rename_success = input.rename(new_filename);
- QLOG_DEBUG() << " Doesn't exist, copying to" << new_filename << ":"
- << QString::number(rename_success);
- if (rename_success)
- successes++;
- else
- failures++;
- }
- else
- {
- input.remove();
- QLOG_DEBUG() << " Already exists, deleting original and not copying.";
- }
+ found++;
}
}
- if (successes + failures == 0)
- {
- QLOG_DEBUG() << "No legacy assets needed importing.";
- }
- else
- {
- QLOG_DEBUG() << "Finished copying legacy assets:" << successes << "successes and"
- << failures << "failures.";
-
- QDirIterator cleanup_iterator(assets_dir);
-
- while (cleanup_iterator.hasNext())
- {
- QString currentDir = cleanup_iterator.next();
- currentDir = currentDir.remove(0, base_length + 1);
-
- bool ignore = false;
- for (QString blacklisted : blacklist)
- {
- if (currentDir.startsWith(blacklisted))
- ignore = true;
- }
-
- if (cleanup_iterator.fileInfo().isDir() && !ignore)
- {
- QString path = cleanup_iterator.filePath();
- QDir folder(path);
-
- QLOG_DEBUG() << "Cleaning up legacy assets folder:" << path;
-
- folder.removeRecursively();
- }
- }
- }
+ return found;
}
/*
diff --git a/logic/assets/AssetsUtils.h b/logic/assets/AssetsUtils.h
index 5276d5a5..aaacc2db 100644
--- a/logic/assets/AssetsUtils.h
+++ b/logic/assets/AssetsUtils.h
@@ -34,6 +34,6 @@ struct AssetsIndex
namespace AssetsUtils
{
-void migrateOldAssets();
bool loadAssetsIndexJson(QString file, AssetsIndex* index);
+int findLegacyAssets();
}
diff --git a/logic/auth/MojangAccount.cpp b/logic/auth/MojangAccount.cpp
index bc6af98f..a462eda5 100644
--- a/logic/auth/MojangAccount.cpp
+++ b/logic/auth/MojangAccount.cpp
@@ -32,7 +32,8 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
// The JSON object must at least have a username for it to be valid.
if (!object.value("username").isString())
{
- QLOG_ERROR() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
+ QLOG_ERROR() << "Can't load Mojang account info from JSON object. Username field is "
+ "missing or of the wrong type.";
return nullptr;
}
@@ -43,7 +44,8 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
QJsonArray profileArray = object.value("profiles").toArray();
if (profileArray.size() < 1)
{
- QLOG_ERROR() << "Can't load Mojang account with username \"" << username << "\". No profiles found.";
+ QLOG_ERROR() << "Can't load Mojang account with username \"" << username
+ << "\". No profiles found.";
return nullptr;
}
@@ -63,7 +65,7 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
}
MojangAccountPtr account(new MojangAccount());
- if(object.value("user").isObject())
+ if (object.value("user").isObject())
{
User u;
QJsonObject userStructure = object.value("user").toObject();
@@ -92,7 +94,7 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object)
return account;
}
-MojangAccountPtr MojangAccount::createFromUsername(const QString& username)
+MojangAccountPtr MojangAccount::createFromUsername(const QString &username)
{
MojangAccountPtr account(new MojangAccount());
account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]"));
@@ -152,27 +154,27 @@ bool MojangAccount::setCurrentProfile(const QString &profileId)
return false;
}
-const AccountProfile* MojangAccount::currentProfile() const
+const AccountProfile *MojangAccount::currentProfile() const
{
- if(m_currentProfile == -1)
+ if (m_currentProfile == -1)
return nullptr;
return &m_profiles[m_currentProfile];
}
AccountStatus MojangAccount::accountStatus() const
{
- if(m_accessToken.isEmpty())
+ if (m_accessToken.isEmpty())
return NotVerified;
- if(!m_online)
+ if (!m_online)
return Verified;
return Online;
}
-std::shared_ptr<Task> MojangAccount::login(QString password)
+std::shared_ptr<YggdrasilTask> MojangAccount::login(QString password)
{
- if(m_currentTask)
+ if (m_currentTask)
return m_currentTask;
- if(password.isEmpty())
+ if (password.isEmpty())
{
m_currentTask.reset(new RefreshTask(this));
}
@@ -196,7 +198,7 @@ void MojangAccount::authFailed(QString reason)
{
// This is emitted when the yggdrasil tasks time out or are cancelled.
// -> we treat the error as no-op
- if(reason != "Yggdrasil task cancelled.")
+ if (reason != "Yggdrasil task cancelled.")
{
m_online = false;
m_accessToken = QString();
diff --git a/logic/auth/MojangAccount.h b/logic/auth/MojangAccount.h
index 325aa826..dd5d54ae 100644
--- a/logic/auth/MojangAccount.h
+++ b/logic/auth/MojangAccount.h
@@ -95,7 +95,7 @@ public: /* manipulation */
* Attempt to login. Empty password means we use the token.
* If the attempt fails because we already are performing some task, it returns false.
*/
- std::shared_ptr<Task> login(QString password = QString());
+ std::shared_ptr<YggdrasilTask> login(QString password = QString());
void downgrade()
{
diff --git a/logic/auth/YggdrasilTask.cpp b/logic/auth/YggdrasilTask.cpp
index 088e1fc0..277d7bfd 100644
--- a/logic/auth/YggdrasilTask.cpp
+++ b/logic/auth/YggdrasilTask.cpp
@@ -48,6 +48,7 @@ void YggdrasilTask::executeTask()
connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply);
connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers);
connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers);
+ connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors);
timeout_keeper.setSingleShot(true);
timeout_keeper.start(timeout_max);
counter.setSingleShot(false);
@@ -75,16 +76,45 @@ void YggdrasilTask::abort()
m_netReply->abort();
}
+void YggdrasilTask::sslErrors(QList<QSslError> errors)
+{
+ int i = 1;
+ for (auto error : errors)
+ {
+ QLOG_ERROR() << "LOGIN SSL Error #" << i << " : " << error.errorString();
+ auto cert = error.certificate();
+ QLOG_ERROR() << "Certificate in question:\n" << cert.toText();
+ i++;
+ }
+}
+
void YggdrasilTask::processReply()
{
setStatus(getStateMessage(STATE_PROCESSING_RESPONSE));
+ if (m_netReply->error() == QNetworkReply::SslHandshakeFailedError)
+ {
+ emitFailed(
+ tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
+ "<ul>"
+ "<li>You use Windows XP and need to <a "
+ "href=\"http://www.microsoft.com/en-us/download/details.aspx?id=38918\">update "
+ "your root certificates</a></li>"
+ "<li>Some device on your network is interfering with SSL traffic. In that case, "
+ "you have bigger worries than Minecraft not starting.</li>"
+ "<li>Possibly something else. Check the MultiMC log file for details</li>"
+ "</ul>"));
+ return;
+ }
+
// any network errors lead to offline mode right now
if (m_netReply->error() >= QNetworkReply::ConnectionRefusedError &&
m_netReply->error() <= QNetworkReply::UnknownNetworkError)
{
// WARNING/FIXME: the value here is used in MojangAccount to detect the cancel/timeout
emitFailed("Yggdrasil task cancelled.");
+ QLOG_ERROR() << "Yggdrasil task cancelled because of: " << m_netReply->error() << " : "
+ << m_netReply->errorString();
return;
}
@@ -172,10 +202,10 @@ QString YggdrasilTask::getStateMessage(const YggdrasilTask::State state) const
switch (state)
{
case STATE_SENDING_REQUEST:
- return tr("Sending request to auth servers.");
+ return tr("Sending request to auth servers...");
case STATE_PROCESSING_RESPONSE:
- return tr("Processing response from servers.");
+ return tr("Processing response from servers...");
default:
- return tr("Processing. Please wait.");
+ return tr("Processing. Please wait...");
}
}
diff --git a/logic/auth/YggdrasilTask.h b/logic/auth/YggdrasilTask.h
index 1f81a2d0..85f5a1e1 100644
--- a/logic/auth/YggdrasilTask.h
+++ b/logic/auth/YggdrasilTask.h
@@ -20,6 +20,7 @@
#include <QString>
#include <QJsonObject>
#include <QTimer>
+#include <qsslerror.h>
#include "logic/auth/MojangAccount.h"
@@ -99,6 +100,7 @@ slots:
void processReply();
void refreshTimers(qint64, qint64);
void heartbeat();
+ void sslErrors(QList<QSslError>);
public
slots:
diff --git a/logic/auth/flows/AuthenticateTask.cpp b/logic/auth/flows/AuthenticateTask.cpp
index f60be35d..6548c4e9 100644
--- a/logic/auth/flows/AuthenticateTask.cpp
+++ b/logic/auth/flows/AuthenticateTask.cpp
@@ -194,9 +194,9 @@ QString AuthenticateTask::getStateMessage(const YggdrasilTask::State state) cons
switch (state)
{
case STATE_SENDING_REQUEST:
- return tr("Authenticating: Sending request.");
+ return tr("Authenticating: Sending request...");
case STATE_PROCESSING_RESPONSE:
- return tr("Authenticating: Processing response.");
+ return tr("Authenticating: Processing response...");
default:
return YggdrasilTask::getStateMessage(state);
}
diff --git a/logic/auth/flows/RefreshTask.cpp b/logic/auth/flows/RefreshTask.cpp
index 5f68ccc7..f63c736e 100644
--- a/logic/auth/flows/RefreshTask.cpp
+++ b/logic/auth/flows/RefreshTask.cpp
@@ -145,9 +145,9 @@ QString RefreshTask::getStateMessage(const YggdrasilTask::State state) const
switch (state)
{
case STATE_SENDING_REQUEST:
- return tr("Refreshing login token.");
+ return tr("Refreshing login token...");
case STATE_PROCESSING_RESPONSE:
- return tr("Refreshing login token: Processing response.");
+ return tr("Refreshing login token: Processing response...");
default:
return YggdrasilTask::getStateMessage(state);
}
diff --git a/logic/auth/flows/ValidateTask.cpp b/logic/auth/flows/ValidateTask.cpp
index 84d5e703..4f7323fd 100644
--- a/logic/auth/flows/ValidateTask.cpp
+++ b/logic/auth/flows/ValidateTask.cpp
@@ -55,9 +55,9 @@ QString ValidateTask::getStateMessage(const YggdrasilTask::State state) const
switch (state)
{
case STATE_SENDING_REQUEST:
- return tr("Validating Access Token: Sending request.");
+ return tr("Validating access token: Sending request...");
case STATE_PROCESSING_RESPONSE:
- return tr("Validating Access Token: Processing response.");
+ return tr("Validating access token: Processing response...");
default:
return YggdrasilTask::getStateMessage(state);
}
diff --git a/logic/icons/IconList.cpp b/logic/icons/IconList.cpp
new file mode 100644
index 00000000..cda2db7b
--- /dev/null
+++ b/logic/icons/IconList.cpp
@@ -0,0 +1,351 @@
+/* Copyright 2013 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 "IconList.h"
+#include <pathutils.h>
+#include <settingsobject.h>
+#include <QMap>
+#include <QEventLoop>
+#include <QMimeData>
+#include <QUrl>
+#include <QFileSystemWatcher>
+#include <MultiMC.h>
+#include <setting.h>
+
+#define MAX_SIZE 1024
+
+IconList::IconList(QObject *parent) : QAbstractListModel(parent)
+{
+ // add builtin icons
+ QDir instance_icons(":/icons/instances/");
+ auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
+ for (auto file_info : file_info_list)
+ {
+ QString key = file_info.baseName();
+ addIcon(key, key, file_info.absoluteFilePath(), MMCIcon::Builtin);
+ }
+
+ m_watcher.reset(new QFileSystemWatcher());
+ is_watching = false;
+ connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
+ SLOT(directoryChanged(QString)));
+ connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
+
+ auto setting = MMC->settings()->getSetting("IconsDir");
+ QString path = setting->get().toString();
+ connect(setting.get(), SIGNAL(settingChanged(const Setting &, QVariant)),
+ SLOT(settingChanged(const Setting &, QVariant)));
+ directoryChanged(path);
+}
+
+void IconList::directoryChanged(const QString &path)
+{
+ QDir new_dir (path);
+ if(m_dir.absolutePath() != new_dir.absolutePath())
+ {
+ m_dir.setPath(path);
+ m_dir.refresh();
+ if(is_watching)
+ stopWatching();
+ startWatching();
+ }
+ if(!m_dir.exists())
+ if(!ensureFolderPathExists(m_dir.absolutePath()))
+ return;
+ m_dir.refresh();
+ auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
+ for (auto it = new_list.begin(); it != new_list.end(); it++)
+ {
+ QString &foo = (*it);
+ foo = m_dir.filePath(foo);
+ }
+ auto new_set = new_list.toSet();
+ QList<QString> current_list;
+ for (auto &it : icons)
+ {
+ if (!it.has(MMCIcon::FileBased))
+ continue;
+ current_list.push_back(it.m_images[MMCIcon::FileBased].filename);
+ }
+ QSet<QString> current_set = current_list.toSet();
+
+ QSet<QString> to_remove = current_set;
+ to_remove -= new_set;
+
+ QSet<QString> to_add = new_set;
+ to_add -= current_set;
+
+ for (auto remove : to_remove)
+ {
+ QLOG_INFO() << "Removing " << remove;
+ QFileInfo rmfile(remove);
+ QString key = rmfile.baseName();
+ int idx = getIconIndex(key);
+ if (idx == -1)
+ continue;
+ icons[idx].remove(MMCIcon::FileBased);
+ if (icons[idx].type() == MMCIcon::ToBeDeleted)
+ {
+ beginRemoveRows(QModelIndex(), idx, idx);
+ icons.remove(idx);
+ reindex();
+ endRemoveRows();
+ }
+ else
+ {
+ dataChanged(index(idx), index(idx));
+ }
+ m_watcher->removePath(remove);
+ emit iconUpdated(key);
+ }
+
+ for (auto add : to_add)
+ {
+ QLOG_INFO() << "Adding " << add;
+ QFileInfo addfile(add);
+ QString key = addfile.baseName();
+ if (addIcon(key, QString(), addfile.filePath(), MMCIcon::FileBased))
+ {
+ m_watcher->addPath(add);
+ emit iconUpdated(key);
+ }
+ }
+}
+
+void IconList::fileChanged(const QString &path)
+{
+ QLOG_INFO() << "Checking " << path;
+ QFileInfo checkfile(path);
+ if (!checkfile.exists())
+ return;
+ QString key = checkfile.baseName();
+ int idx = getIconIndex(key);
+ if (idx == -1)
+ return;
+ QIcon icon(path);
+ if (!icon.availableSizes().size())
+ return;
+
+ icons[idx].m_images[MMCIcon::FileBased].icon = icon;
+ dataChanged(index(idx), index(idx));
+ emit iconUpdated(key);
+}
+
+void IconList::settingChanged(const Setting &setting, QVariant value)
+{
+ if(setting.id() != "IconsDir")
+ return;
+
+ directoryChanged(value.toString());
+}
+
+void IconList::startWatching()
+{
+ auto abs_path = m_dir.absolutePath();
+ ensureFolderPathExists(abs_path);
+ is_watching = m_watcher->addPath(abs_path);
+ if (is_watching)
+ {
+ QLOG_INFO() << "Started watching " << abs_path;
+ }
+ else
+ {
+ QLOG_INFO() << "Failed to start watching " << abs_path;
+ }
+}
+
+void IconList::stopWatching()
+{
+ m_watcher->removePaths(m_watcher->files());
+ m_watcher->removePaths(m_watcher->directories());
+ is_watching = false;
+}
+
+QStringList IconList::mimeTypes() const
+{
+ QStringList types;
+ types << "text/uri-list";
+ return types;
+}
+Qt::DropActions IconList::supportedDropActions() const
+{
+ return Qt::CopyAction;
+}
+
+bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
+ const QModelIndex &parent)
+{
+ 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();
+ QStringList iconFiles;
+ for (auto url : urls)
+ {
+ // only local files may be dropped...
+ if (!url.isLocalFile())
+ continue;
+ iconFiles += url.toLocalFile();
+ }
+ installIcons(iconFiles);
+ return true;
+ }
+ return false;
+}
+
+Qt::ItemFlags IconList::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
+ if (index.isValid())
+ return Qt::ItemIsDropEnabled | defaultFlags;
+ else
+ return Qt::ItemIsDropEnabled | defaultFlags;
+}
+
+QVariant IconList::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ int row = index.row();
+
+ if (row < 0 || row >= icons.size())
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DecorationRole:
+ return icons[row].icon();
+ case Qt::DisplayRole:
+ return icons[row].name();
+ case Qt::UserRole:
+ return icons[row].m_key;
+ default:
+ return QVariant();
+ }
+}
+
+int IconList::rowCount(const QModelIndex &parent) const
+{
+ return icons.size();
+}
+
+void IconList::installIcons(QStringList iconFiles)
+{
+ for (QString file : iconFiles)
+ {
+ QFileInfo fileinfo(file);
+ if (!fileinfo.isReadable() || !fileinfo.isFile())
+ continue;
+ QString target = PathCombine("icons", fileinfo.fileName());
+
+ QString suffix = fileinfo.suffix();
+ if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
+ continue;
+
+ if (!QFile::copy(file, target))
+ continue;
+ }
+}
+
+bool IconList::deleteIcon(QString key)
+{
+ int iconIdx = getIconIndex(key);
+ if (iconIdx == -1)
+ return false;
+ auto &iconEntry = icons[iconIdx];
+ if (iconEntry.has(MMCIcon::FileBased))
+ {
+ return QFile::remove(iconEntry.m_images[MMCIcon::FileBased].filename);
+ }
+ return false;
+}
+
+bool IconList::addIcon(QString key, QString name, QString path, MMCIcon::Type type)
+{
+ // replace the icon even? is the input valid?
+ QIcon icon(path);
+ if (!icon.availableSizes().size())
+ return false;
+ auto iter = name_index.find(key);
+ if (iter != name_index.end())
+ {
+ auto &oldOne = icons[*iter];
+ oldOne.replace(type, icon, path);
+ dataChanged(index(*iter), index(*iter));
+ return true;
+ }
+ else
+ {
+ // add a new icon
+ beginInsertRows(QModelIndex(), icons.size(), icons.size());
+ {
+ MMCIcon mmc_icon;
+ mmc_icon.m_name = name;
+ mmc_icon.m_key = key;
+ mmc_icon.replace(type, icon, path);
+ icons.push_back(mmc_icon);
+ name_index[key] = icons.size() - 1;
+ }
+ endInsertRows();
+ return true;
+ }
+}
+
+void IconList::reindex()
+{
+ name_index.clear();
+ int i = 0;
+ for (auto &iter : icons)
+ {
+ name_index[iter.m_key] = i;
+ i++;
+ }
+}
+
+QIcon IconList::getIcon(QString key)
+{
+ int icon_index = getIconIndex(key);
+
+ if (icon_index != -1)
+ return icons[icon_index].icon();
+
+ // Fallback for icons that don't exist.
+ icon_index = getIconIndex("infinity");
+
+ if (icon_index != -1)
+ return icons[icon_index].icon();
+ return QIcon();
+}
+
+int IconList::getIconIndex(QString key)
+{
+ if (key == "default")
+ key = "infinity";
+
+ auto iter = name_index.find(key);
+ if (iter != name_index.end())
+ return *iter;
+
+ return -1;
+}
+
+//#include "IconList.moc"
diff --git a/logic/lists/IconList.h b/logic/icons/IconList.h
index 40ad043b..322411d1 100644
--- a/logic/lists/IconList.h
+++ b/logic/icons/IconList.h
@@ -17,15 +17,21 @@
#include <QMutex>
#include <QAbstractListModel>
+#include <QFile>
+#include <QDir>
#include <QtGui/QIcon>
+#include <memory>
+#include "MMCIcon.h"
+#include "setting.h"
-class Private;
+class QFileSystemWatcher;
class IconList : public QAbstractListModel
{
+ Q_OBJECT
public:
- IconList();
- virtual ~IconList();
+ explicit IconList(QObject *parent = 0);
+ virtual ~IconList() {};
QIcon getIcon(QString key);
int getIconIndex(QString key);
@@ -33,7 +39,7 @@ public:
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
- bool addIcon(QString key, QString name, QString path, bool is_builtin = false);
+ bool addIcon(QString key, QString name, QString path, MMCIcon::Type type);
bool deleteIcon(QString key);
virtual QStringList mimeTypes() const;
@@ -44,11 +50,28 @@ public:
void installIcons(QStringList iconFiles);
+ void startWatching();
+ void stopWatching();
+
+signals:
+ void iconUpdated(QString key);
+
private:
// hide copy constructor
IconList(const IconList &) = delete;
// hide assign op
IconList &operator=(const IconList &) = delete;
void reindex();
- Private *d;
+
+protected
+slots:
+ void directoryChanged(const QString &path);
+ void fileChanged(const QString &path);
+ void settingChanged(const Setting & setting, QVariant value);
+private:
+ std::shared_ptr<QFileSystemWatcher> m_watcher;
+ bool is_watching;
+ QMap<QString, int> name_index;
+ QVector<MMCIcon> icons;
+ QDir m_dir;
};
diff --git a/logic/icons/MMCIcon.cpp b/logic/icons/MMCIcon.cpp
new file mode 100644
index 00000000..d721513d
--- /dev/null
+++ b/logic/icons/MMCIcon.cpp
@@ -0,0 +1,89 @@
+/* Copyright 2013 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 "MMCIcon.h"
+#include <QFileInfo>
+
+MMCIcon::Type operator--(MMCIcon::Type &t, int)
+{
+ MMCIcon::Type temp = t;
+ switch (t)
+ {
+ case MMCIcon::Type::Builtin:
+ t = MMCIcon::Type::ToBeDeleted;
+ break;
+ case MMCIcon::Type::Transient:
+ t = MMCIcon::Type::Builtin;
+ break;
+ case MMCIcon::Type::FileBased:
+ t = MMCIcon::Type::Transient;
+ break;
+ default:
+ {
+ }
+ }
+ return temp;
+}
+
+MMCIcon::Type MMCIcon::type() const
+{
+ return m_current_type;
+}
+
+QString MMCIcon::name() const
+{
+ if (m_name.size())
+ return m_name;
+ return m_key;
+}
+
+bool MMCIcon::has(MMCIcon::Type _type) const
+{
+ return m_images[_type].present();
+}
+
+QIcon MMCIcon::icon() const
+{
+ if (m_current_type == Type::ToBeDeleted)
+ return QIcon();
+ return m_images[m_current_type].icon;
+}
+
+void MMCIcon::remove(Type rm_type)
+{
+ m_images[rm_type].filename = QString();
+ m_images[rm_type].icon = QIcon();
+ for (auto iter = rm_type; iter != Type::ToBeDeleted; iter--)
+ {
+ if (m_images[iter].present())
+ {
+ m_current_type = iter;
+ return;
+ }
+ }
+ m_current_type = Type::ToBeDeleted;
+}
+
+void MMCIcon::replace(MMCIcon::Type new_type, QIcon icon, QString path)
+{
+ QFileInfo foo(path);
+ if (new_type > m_current_type || m_current_type == MMCIcon::ToBeDeleted)
+ {
+ m_current_type = new_type;
+ }
+ m_images[new_type].icon = icon;
+ m_images[new_type].changed = foo.lastModified();
+ m_images[new_type].filename = path;
+}
diff --git a/logic/icons/MMCIcon.h b/logic/icons/MMCIcon.h
new file mode 100644
index 00000000..5e4b3bb6
--- /dev/null
+++ b/logic/icons/MMCIcon.h
@@ -0,0 +1,52 @@
+/* Copyright 2013 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.
+ */
+
+#pragma once
+#include <QString>
+#include <QDateTime>
+#include <QIcon>
+struct MMCImage
+{
+ QIcon icon;
+ QString filename;
+ QDateTime changed;
+ bool present() const
+ {
+ return !icon.isNull();
+ }
+};
+
+struct MMCIcon
+{
+ enum Type : unsigned
+ {
+ Builtin,
+ Transient,
+ FileBased,
+ ICONS_TOTAL,
+ ToBeDeleted
+ };
+ QString m_key;
+ QString m_name;
+ MMCImage m_images[ICONS_TOTAL];
+ Type m_current_type = ToBeDeleted;
+
+ Type type() const;
+ QString name() const;
+ bool has(Type _type) const;
+ QIcon icon() const;
+ void remove(Type rm_type);
+ void replace(Type new_type, QIcon icon, QString path = QString());
+};
diff --git a/logic/lists/ForgeVersionList.cpp b/logic/lists/ForgeVersionList.cpp
index b5e421af..56eca744 100644
--- a/logic/lists/ForgeVersionList.cpp
+++ b/logic/lists/ForgeVersionList.cpp
@@ -15,6 +15,7 @@
#include "ForgeVersionList.h"
#include <logic/net/NetJob.h>
+#include <logic/net/URLConstants.h>
#include "MultiMC.h"
#include <QtNetwork>
@@ -23,8 +24,6 @@
#include "logger/QsLog.h"
-#define JSON_URL "http://files.minecraftforge.net/minecraftforge/json"
-
ForgeVersionList::ForgeVersionList(QObject *parent) : BaseVersionList(parent)
{
}
@@ -159,44 +158,43 @@ ForgeListLoadTask::ForgeListLoadTask(ForgeVersionList *vlist) : Task()
void ForgeListLoadTask::executeTask()
{
+ setStatus(tr("Fetching Forge version lists..."));
auto job = new NetJob("Version index");
// we do not care if the version is stale or not.
auto forgeListEntry = MMC->metacache()->resolveEntry("minecraftforge", "list.json");
+ auto gradleForgeListEntry = MMC->metacache()->resolveEntry("minecraftforge", "json");
// verify by poking the server.
forgeListEntry->stale = true;
+ gradleForgeListEntry->stale = true;
+
+ job->addNetAction(listDownload = CacheDownload::make(QUrl(URLConstants::FORGE_LEGACY_URL),
+ forgeListEntry));
+ job->addNetAction(gradleListDownload = CacheDownload::make(
+ QUrl(URLConstants::FORGE_GRADLE_URL), gradleForgeListEntry));
+
+ connect(listDownload.get(), SIGNAL(failed(int)), SLOT(listFailed()));
+ connect(gradleListDownload.get(), SIGNAL(failed(int)), SLOT(gradleListFailed()));
- job->addNetAction(CacheDownload::make(QUrl(JSON_URL), forgeListEntry));
listJob.reset(job);
- connect(listJob.get(), SIGNAL(succeeded()), SLOT(list_downloaded()));
- connect(listJob.get(), SIGNAL(failed()), SLOT(list_failed()));
+ connect(listJob.get(), SIGNAL(succeeded()), SLOT(listDownloaded()));
connect(listJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
listJob->start();
}
-void ForgeListLoadTask::list_failed()
-{
- auto DlJob = listJob->first();
- auto reply = DlJob->m_reply;
- if (reply)
- {
- QLOG_ERROR() << "Getting forge version list failed: " << reply->errorString();
- }
- else
- QLOG_ERROR() << "Getting forge version list failed for reasons unknown.";
-}
-
-void ForgeListLoadTask::list_downloaded()
+bool ForgeListLoadTask::parseForgeList(QList<BaseVersionPtr> &out)
{
QByteArray data;
{
- auto DlJob = listJob->first();
- auto filename = std::dynamic_pointer_cast<CacheDownload>(DlJob)->m_target_path;
+ auto dlJob = listDownload;
+ auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->m_target_path;
QFile listFile(filename);
if (!listFile.open(QIODevice::ReadOnly))
- return;
+ {
+ return false;
+ }
data = listFile.readAll();
- DlJob.reset();
+ dlJob.reset();
}
QJsonParseError jsonError;
@@ -205,13 +203,13 @@ void ForgeListLoadTask::list_downloaded()
if (jsonError.error != QJsonParseError::NoError)
{
emitFailed("Error parsing version list JSON:" + jsonError.errorString());
- return;
+ return false;
}
if (!jsonDoc.isObject())
{
- emitFailed("Error parsing version list JSON: jsonDoc is not an object");
- return;
+ emitFailed("Error parsing version list JSON: JSON root is not an object");
+ return false;
}
QJsonObject root = jsonDoc.object();
@@ -221,11 +219,10 @@ void ForgeListLoadTask::list_downloaded()
{
emitFailed(
"Error parsing version list JSON: version list object is missing 'builds' array");
- return;
+ return false;
}
QJsonArray builds = root.value("builds").toArray();
- QList<BaseVersionPtr> tempList;
for (int i = 0; i < builds.count(); i++)
{
// Load the version info.
@@ -246,7 +243,9 @@ void ForgeListLoadTask::list_downloaded()
for (int j = 0; j < files.count(); j++)
{
if (!files[j].isObject())
+ {
continue;
+ }
QJsonObject file = files[j].toObject();
buildtype = file.value("buildtype").toString();
if ((buildtype == "client" || buildtype == "universal") && !valid)
@@ -262,7 +261,9 @@ void ForgeListLoadTask::list_downloaded()
{
QString ext = file.value("ext").toString();
if (ext.isEmpty())
+ {
continue;
+ }
changelog_url = file.value("url").toString();
}
else if (buildtype == "installer")
@@ -282,15 +283,161 @@ void ForgeListLoadTask::list_downloaded()
fVersion->jobbuildver = jobbuildver;
fVersion->mcver = mcver;
if (installer_filename.isEmpty())
+ {
fVersion->filename = filename;
+ }
else
+ {
fVersion->filename = installer_filename;
+ }
fVersion->m_buildnr = build_nr;
- tempList.append(fVersion);
+ out.append(fVersion);
+ }
+ }
+
+ return true;
+}
+
+bool ForgeListLoadTask::parseForgeGradleList(QList<BaseVersionPtr> &out)
+{
+ QByteArray data;
+ {
+ auto dlJob = gradleListDownload;
+ auto filename = std::dynamic_pointer_cast<CacheDownload>(dlJob)->m_target_path;
+ QFile listFile(filename);
+ if (!listFile.open(QIODevice::ReadOnly))
+ {
+ return false;
+ }
+ data = listFile.readAll();
+ dlJob.reset();
+ }
+
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
+
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ emitFailed("Error parsing gradle version list JSON:" + jsonError.errorString());
+ return false;
+ }
+
+ if (!jsonDoc.isObject())
+ {
+ emitFailed("Error parsing gradle version list JSON: JSON root is not an object");
+ return false;
+ }
+
+ QJsonObject root = jsonDoc.object();
+
+ // we probably could hard code these, but it might still be worth doing it this way
+ const QString webpath = root.value("webpath").toString();
+ const QString artifact = root.value("artifact").toString();
+
+ QJsonObject numbers = root.value("number").toObject();
+ for (auto it = numbers.begin(); it != numbers.end(); ++it)
+ {
+ QJsonObject number = it.value().toObject();
+ std::shared_ptr<ForgeVersion> fVersion(new ForgeVersion());
+ fVersion->m_buildnr = number.value("build").toDouble();
+ fVersion->jobbuildver = number.value("version").toString();
+ fVersion->mcver = number.value("mcversion").toString();
+ fVersion->filename = "";
+ QString filename, installer_filename;
+ QJsonArray files = number.value("files").toArray();
+ for (auto fIt = files.begin(); fIt != files.end(); ++fIt)
+ {
+ // TODO with gradle we also get checksums, use them
+ QJsonArray file = (*fIt).toArray();
+ if (file.size() < 3)
+ {
+ continue;
+ }
+ if (file.at(1).toString() == "installer")
+ {
+ fVersion->installer_url = QString("%1/%2-%3/%4-%2-%3-installer.%5").arg(
+ webpath, fVersion->mcver, fVersion->jobbuildver, artifact,
+ file.at(0).toString());
+ installer_filename = QString("%1-%2-%3-installer.%4").arg(
+ artifact, fVersion->mcver, fVersion->jobbuildver, file.at(0).toString());
+ }
+ else if (file.at(1).toString() == "universal")
+ {
+ fVersion->universal_url = QString("%1/%2-%3/%4-%2-%3-universal.%5").arg(
+ webpath, fVersion->mcver, fVersion->jobbuildver, artifact,
+ file.at(0).toString());
+ filename = QString("%1-%2-%3-universal.%4").arg(
+ artifact, fVersion->mcver, fVersion->jobbuildver, file.at(0).toString());
+ }
+ else if (file.at(1).toString() == "changelog")
+ {
+ fVersion->changelog_url = QString("%1/%2-%3/%4-%2-%3-changelog.%5").arg(
+ webpath, fVersion->mcver, fVersion->jobbuildver, artifact,
+ file.at(0).toString());
+ }
+ }
+ if (fVersion->installer_url.isEmpty() && fVersion->universal_url.isEmpty())
+ {
+ continue;
}
+ fVersion->filename = fVersion->installer_url.isEmpty() ? filename : installer_filename;
+ out.append(fVersion);
}
- m_list->updateListData(tempList);
+
+ return true;
+}
+
+void ForgeListLoadTask::listDownloaded()
+{
+ QList<BaseVersionPtr> list;
+ bool ret = true;
+ if (!parseForgeList(list))
+ {
+ ret = false;
+ }
+ if (!parseForgeGradleList(list))
+ {
+ ret = false;
+ }
+
+ if (!ret)
+ {
+ return;
+ }
+
+ qSort(list.begin(), list.end(), [](const BaseVersionPtr & p1, const BaseVersionPtr & p2)
+ {
+ // TODO better comparison (takes major/minor/build number into account)
+ return p1->name() > p2->name();
+ });
+
+ m_list->updateListData(list);
emitSucceeded();
return;
}
+
+void ForgeListLoadTask::listFailed()
+{
+ auto reply = listDownload->m_reply;
+ if (reply)
+ {
+ QLOG_ERROR() << "Getting forge version list failed: " << reply->errorString();
+ }
+ else
+ {
+ QLOG_ERROR() << "Getting forge version list failed for reasons unknown.";
+ }
+}
+void ForgeListLoadTask::gradleListFailed()
+{
+ auto reply = gradleListDownload->m_reply;
+ if (reply)
+ {
+ QLOG_ERROR() << "Getting forge version list failed: " << reply->errorString();
+ }
+ else
+ {
+ QLOG_ERROR() << "Getting forge version list failed for reasons unknown.";
+ }
+}
diff --git a/logic/lists/ForgeVersionList.h b/logic/lists/ForgeVersionList.h
index bf9e87b2..924084ae 100644
--- a/logic/lists/ForgeVersionList.h
+++ b/logic/lists/ForgeVersionList.h
@@ -80,7 +80,7 @@ public:
protected:
QList<BaseVersionPtr> m_vlist;
- bool m_loaded;
+ bool m_loaded = false;
protected
slots:
@@ -98,10 +98,18 @@ public:
protected
slots:
- void list_downloaded();
- void list_failed();
+ void listDownloaded();
+ void listFailed();
+ void gradleListFailed();
protected:
NetJobPtr listJob;
ForgeVersionList *m_list;
+
+ CacheDownloadPtr listDownload;
+ CacheDownloadPtr gradleListDownload;
+
+private:
+ bool parseForgeList(QList<BaseVersionPtr> &out);
+ bool parseForgeGradleList(QList<BaseVersionPtr> &out);
};
diff --git a/logic/lists/IconList.cpp b/logic/lists/IconList.cpp
deleted file mode 100644
index ecfb8c3c..00000000
--- a/logic/lists/IconList.cpp
+++ /dev/null
@@ -1,271 +0,0 @@
-/* Copyright 2013 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 "IconList.h"
-#include <pathutils.h>
-#include <QMap>
-#include <QEventLoop>
-#include <QDir>
-#include <QMimeData>
-#include <QUrl>
-#define MAX_SIZE 1024
-
-struct entry
-{
- QString key;
- QString name;
- QIcon icon;
- bool is_builtin;
- QString filename;
-};
-
-class Private : public QObject
-{
- Q_OBJECT
-public:
- QMap<QString, int> index;
- QVector<entry> icons;
- Private()
- {
- }
-};
-
-IconList::IconList() : QAbstractListModel(), d(new Private())
-{
- QDir instance_icons(":/icons/instances/");
- auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
- for (auto file_info : file_info_list)
- {
- QString key = file_info.baseName();
- addIcon(key, key, file_info.absoluteFilePath(), true);
- }
-
- // FIXME: get from settings
- ensureFolderPathExists("icons");
- QDir user_icons("icons");
- file_info_list = user_icons.entryInfoList(QDir::Files, QDir::Name);
- for (auto file_info : file_info_list)
- {
- QString filename = file_info.absoluteFilePath();
- QString key = file_info.baseName();
- addIcon(key, key, filename);
- }
-}
-
-IconList::~IconList()
-{
- delete d;
- d = nullptr;
-}
-
-QStringList IconList::mimeTypes() const
-{
- QStringList types;
- types << "text/uri-list";
- return types;
-}
-Qt::DropActions IconList::supportedDropActions() const
-{
- return Qt::CopyAction;
-}
-
-bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
- const QModelIndex &parent)
-{
- 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())
- {
- /*
- bool was_watching = is_watching;
- if(was_watching)
- stopWatching();
- */
- auto urls = data->urls();
- QStringList iconFiles;
- for (auto url : urls)
- {
- // only local files may be dropped...
- if (!url.isLocalFile())
- continue;
- iconFiles += url.toLocalFile();
- }
- installIcons(iconFiles);
- /*
- if(was_watching)
- startWatching();
- */
- return true;
- }
- return false;
-}
-
-Qt::ItemFlags IconList::flags(const QModelIndex &index) const
-{
- Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
- if (index.isValid())
- return Qt::ItemIsDropEnabled | defaultFlags;
- else
- return Qt::ItemIsDropEnabled | defaultFlags;
-}
-
-QVariant IconList::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- int row = index.row();
-
- if (row < 0 || row >= d->icons.size())
- return QVariant();
-
- switch (role)
- {
- case Qt::DecorationRole:
- return d->icons[row].icon;
- case Qt::DisplayRole:
- return d->icons[row].name;
- case Qt::UserRole:
- return d->icons[row].key;
- default:
- return QVariant();
- }
-}
-
-int IconList::rowCount(const QModelIndex &parent) const
-{
- return d->icons.size();
-}
-
-void IconList::installIcons(QStringList iconFiles)
-{
- for (QString file : iconFiles)
- {
- QFileInfo fileinfo(file);
- if (!fileinfo.isReadable() || !fileinfo.isFile())
- continue;
- QString target = PathCombine("icons", fileinfo.fileName());
-
- QString suffix = fileinfo.suffix();
- if (suffix != "jpeg" && suffix != "png" && suffix != "jpg")
- continue;
-
- if (!QFile::copy(file, target))
- continue;
-
- QString key = fileinfo.baseName();
- addIcon(key, key, target);
- }
-}
-
-bool IconList::deleteIcon(QString key)
-{
- int iconIdx = getIconIndex(key);
- if (iconIdx == -1)
- return false;
- auto &iconEntry = d->icons[iconIdx];
- if (iconEntry.is_builtin)
- return false;
- if (QFile::remove(iconEntry.filename))
- {
- beginRemoveRows(QModelIndex(), iconIdx, iconIdx);
- d->icons.remove(iconIdx);
- reindex();
- endRemoveRows();
- }
- return true;
-}
-
-bool IconList::addIcon(QString key, QString name, QString path, bool is_builtin)
-{
- auto iter = d->index.find(key);
- if (iter != d->index.end())
- {
- if (d->icons[*iter].is_builtin)
- return false;
-
- QIcon icon(path);
- if (icon.isNull())
- return false;
-
- auto &oldOne = d->icons[*iter];
-
- if (!QFile::remove(oldOne.filename))
- return false;
-
- // replace the icon
- oldOne = {key, name, icon, is_builtin, path};
- dataChanged(index(*iter), index(*iter));
- return true;
- }
- else
- {
- QIcon icon(path);
- if (icon.isNull())
- return false;
-
- // add a new icon
- beginInsertRows(QModelIndex(), d->icons.size(), d->icons.size());
- d->icons.push_back({key, name, icon, is_builtin, path});
- d->index[key] = d->icons.size() - 1;
- endInsertRows();
- return true;
- }
-}
-
-void IconList::reindex()
-{
- d->index.clear();
- int i = 0;
- for (auto &iter : d->icons)
- {
- d->index[iter.key] = i;
- i++;
- }
-}
-
-QIcon IconList::getIcon(QString key)
-{
- int icon_index = getIconIndex(key);
-
- if (icon_index != -1)
- return d->icons[icon_index].icon;
-
- // Fallback for icons that don't exist.
- icon_index = getIconIndex("infinity");
-
- if (icon_index != -1)
- return d->icons[icon_index].icon;
- return QIcon();
-}
-
-int IconList::getIconIndex(QString key)
-{
- if (key == "default")
- key = "infinity";
-
- auto iter = d->index.find(key);
- if (iter != d->index.end())
- return *iter;
-
- return -1;
-}
-
-#include "IconList.moc" \ No newline at end of file
diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp
index 15fd10ba..48a2865a 100644
--- a/logic/lists/InstanceList.cpp
+++ b/logic/lists/InstanceList.cpp
@@ -22,11 +22,14 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
+#include <QXmlStreamReader>
+#include <QRegularExpression>
#include <pathutils.h>
#include "MultiMC.h"
#include "logic/lists/InstanceList.h"
-#include "logic/lists/IconList.h"
+#include "logic/icons/IconList.h"
+#include "logic/lists/MinecraftVersionList.h"
#include "logic/BaseInstance.h"
#include "logic/InstanceFactory.h"
#include "logger/QsLog.h"
@@ -42,6 +45,9 @@ InstanceList::InstanceList(const QString &instDir, QObject *parent)
{
QDir::current().mkpath(m_instDir);
}
+
+ connect(MMC->minecraftlist().get(), &MinecraftVersionList::modelReset, this,
+ &InstanceList::loadList);
}
InstanceList::~InstanceList()
@@ -276,66 +282,155 @@ void InstanceList::loadGroupList(QMap<QString, QString> &groupMap)
}
}
-InstanceList::InstListError InstanceList::loadList()
+struct FTBRecord
{
- // load the instance groups
- QMap<QString, QString> groupMap;
- loadGroupList(groupMap);
+ QString dir;
+ QString name;
+ QString logo;
+ QString mcVersion;
+ QString description;
+};
+
+void InstanceList::loadForgeInstances(QMap<QString, QString> groupMap)
+{
+ QList<FTBRecord> records;
+ QDir dir = QDir(MMC->settings()->get("FTBLauncherRoot").toString());
+ QDir dataDir = QDir(MMC->settings()->get("FTBRoot").toString());
+ if (!dir.exists())
+ {
+ QLOG_INFO() << "The FTB launcher directory specified does not exist. Please check your "
+ "settings.";
+ return;
+ }
+ else if (!dataDir.exists())
+ {
+ QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings";
+ return;
+ }
- beginResetModel();
+ dir.cd("ModPacks");
+ QFile f(dir.absoluteFilePath("modpacks.xml"));
+ if (!f.open(QFile::ReadOnly))
+ return;
- m_instances.clear();
- QDir dir(m_instDir);
- QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
- QDirIterator::FollowSymlinks);
- while (iter.hasNext())
+ // read the FTB packs XML.
+ QXmlStreamReader reader(&f);
+ while (!reader.atEnd())
{
- QString subDir = iter.next();
- if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists())
+ switch (reader.readNext())
+ {
+ case QXmlStreamReader::StartElement:
+ {
+ if (reader.name() == "modpack")
+ {
+ QXmlStreamAttributes attrs = reader.attributes();
+ FTBRecord record;
+ record.dir = attrs.value("dir").toString();
+ record.name = attrs.value("name").toString();
+ record.logo = attrs.value("logo").toString();
+ record.mcVersion = attrs.value("mcVersion").toString();
+ record.description = attrs.value("description").toString();
+ records.append(record);
+ }
+ break;
+ }
+ case QXmlStreamReader::EndElement:
+ break;
+ case QXmlStreamReader::Characters:
+ break;
+ default:
+ break;
+ }
+ }
+ f.close();
+
+ // process the records we acquired.
+ for (auto record : records)
+ {
+ auto instanceDir = dataDir.absoluteFilePath(record.dir);
+ auto templateDir = dir.absoluteFilePath(record.dir);
+ if (!QFileInfo(instanceDir).exists())
+ {
continue;
+ }
- BaseInstance *instPtr = NULL;
- auto &loader = InstanceFactory::get();
- auto error = loader.loadInstance(instPtr, subDir);
+ QString iconKey = record.logo;
+ iconKey.remove(QRegularExpression("\\..*"));
+ MMC->icons()->addIcon(iconKey, iconKey, PathCombine(templateDir, record.logo),
+ MMCIcon::Transient);
- if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance)
+ if (!QFileInfo(PathCombine(instanceDir, "instance.cfg")).exists())
{
- QString errorMsg = QString("Failed to load instance %1: ")
- .arg(QFileInfo(subDir).baseName())
- .toUtf8();
-
- switch (error)
+ BaseInstance *instPtr = NULL;
+ auto &factory = InstanceFactory::get();
+ auto version = MMC->minecraftlist()->findVersion(record.mcVersion);
+ if (!version)
{
- default:
- errorMsg += QString("Unknown instance loader error %1").arg(error);
- break;
+ QLOG_ERROR() << "Can't load instance " << instanceDir
+ << " because minecraft version " << record.mcVersion
+ << " can't be resolved.";
+ continue;
}
- QLOG_ERROR() << errorMsg.toUtf8();
+ auto error = factory.createInstance(instPtr, version, instanceDir,
+ InstanceFactory::FTBInstance);
+
+ if (!instPtr || error != InstanceFactory::NoCreateError)
+ continue;
+
+ instPtr->setGroupInitial("FTB");
+ instPtr->setName(record.name);
+ instPtr->setIconKey(iconKey);
+ instPtr->setIntendedVersionId(record.mcVersion);
+ instPtr->setNotes(record.description);
+ continueProcessInstance(instPtr, error, instanceDir, groupMap);
}
- else if (!instPtr)
+ else
{
- QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.")
- .arg(QFileInfo(subDir).baseName())
- .toUtf8();
+ BaseInstance *instPtr = NULL;
+ auto error = InstanceFactory::get().loadInstance(instPtr, instanceDir);
+ if (!instPtr || error != InstanceFactory::NoCreateError)
+ continue;
+ instPtr->setGroupInitial("FTB");
+ instPtr->setName(record.name);
+ instPtr->setIconKey(iconKey);
+ if (instPtr->intendedVersionId() != record.mcVersion)
+ instPtr->setIntendedVersionId(record.mcVersion);
+ instPtr->setNotes(record.description);
+ continueProcessInstance(instPtr, error, instanceDir, groupMap);
}
- else
+ }
+}
+
+InstanceList::InstListError InstanceList::loadList()
+{
+ // load the instance groups
+ QMap<QString, QString> groupMap;
+ loadGroupList(groupMap);
+
+ beginResetModel();
+
+ m_instances.clear();
+
+ {
+ QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable,
+ QDirIterator::FollowSymlinks);
+ while (iter.hasNext())
{
- std::shared_ptr<BaseInstance> inst(instPtr);
- auto iter = groupMap.find(inst->id());
- if (iter != groupMap.end())
- {
- inst->setGroupInitial((*iter));
- }
- QLOG_INFO() << "Loaded instance " << inst->name();
- inst->setParent(this);
- m_instances.append(inst);
- connect(instPtr, SIGNAL(propertiesChanged(BaseInstance *)), this,
- SLOT(propertiesChanged(BaseInstance *)));
- connect(instPtr, SIGNAL(groupChanged()), this, SLOT(groupChanged()));
- connect(instPtr, SIGNAL(nuked(BaseInstance *)), this,
- SLOT(instanceNuked(BaseInstance *)));
+ QString subDir = iter.next();
+ if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists())
+ continue;
+
+ BaseInstance *instPtr = NULL;
+ auto error = InstanceFactory::get().loadInstance(instPtr, subDir);
+ continueProcessInstance(instPtr, error, subDir, groupMap);
}
}
+
+ if (MMC->settings()->get("TrackFTBInstances").toBool())
+ {
+ loadForgeInstances(groupMap);
+ }
+
endResetModel();
emit dataIsInvalid();
return NoError;
@@ -409,6 +504,47 @@ int InstanceList::getInstIndex(BaseInstance *inst) const
return -1;
}
+void InstanceList::continueProcessInstance(BaseInstance *instPtr, const int error,
+ const QDir &dir, QMap<QString, QString> &groupMap)
+{
+ if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance)
+ {
+ QString errorMsg = QString("Failed to load instance %1: ")
+ .arg(QFileInfo(dir.absolutePath()).baseName())
+ .toUtf8();
+
+ switch (error)
+ {
+ default:
+ errorMsg += QString("Unknown instance loader error %1").arg(error);
+ break;
+ }
+ QLOG_ERROR() << errorMsg.toUtf8();
+ }
+ else if (!instPtr)
+ {
+ QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.")
+ .arg(QFileInfo(dir.absolutePath()).baseName())
+ .toUtf8();
+ }
+ else
+ {
+ auto iter = groupMap.find(instPtr->id());
+ if (iter != groupMap.end())
+ {
+ instPtr->setGroupInitial((*iter));
+ }
+ QLOG_INFO() << "Loaded instance " << instPtr->name();
+ instPtr->setParent(this);
+ m_instances.append(std::shared_ptr<BaseInstance>(instPtr));
+ connect(instPtr, SIGNAL(propertiesChanged(BaseInstance *)), this,
+ SLOT(propertiesChanged(BaseInstance *)));
+ connect(instPtr, SIGNAL(groupChanged()), this, SLOT(groupChanged()));
+ connect(instPtr, SIGNAL(nuked(BaseInstance *)), this,
+ SLOT(instanceNuked(BaseInstance *)));
+ }
+}
+
void InstanceList::instanceNuked(BaseInstance *inst)
{
int i = getInstIndex(inst);
diff --git a/logic/lists/InstanceList.h b/logic/lists/InstanceList.h
index f23b7763..0ce808e5 100644
--- a/logic/lists/InstanceList.h
+++ b/logic/lists/InstanceList.h
@@ -25,6 +25,8 @@
class BaseInstance;
+class QDir;
+
class InstanceList : public QAbstractListModel
{
Q_OBJECT
@@ -66,11 +68,6 @@ public:
}
/*!
- * \brief Loads the instance list. Triggers notifications.
- */
- InstListError loadList();
-
- /*!
* \brief Get the instance at index
*/
InstancePtr at(int i) const
@@ -108,6 +105,12 @@ public
slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
+ /*!
+ * \brief Loads the instance list. Triggers notifications.
+ */
+ InstListError loadList();
+ void loadForgeInstances(QMap<QString, QString> groupMap);
+
private
slots:
void propertiesChanged(BaseInstance *inst);
@@ -117,6 +120,9 @@ slots:
private:
int getInstIndex(BaseInstance *inst) const;
+ void continueProcessInstance(BaseInstance *instPtr, const int error, const QDir &dir,
+ QMap<QString, QString> &groupMap);
+
protected:
QString m_instDir;
QList<InstancePtr> m_instances;
diff --git a/logic/lists/JavaVersionList.cpp b/logic/lists/JavaVersionList.cpp
index d2f0972c..e8c5acd0 100644
--- a/logic/lists/JavaVersionList.cpp
+++ b/logic/lists/JavaVersionList.cpp
@@ -172,14 +172,14 @@ JavaListLoadTask::~JavaListLoadTask()
void JavaListLoadTask::executeTask()
{
- setStatus("Detecting Java installations...");
+ setStatus(tr("Detecting Java installations..."));
JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths();
- auto job = new JavaCheckerJob("Java detection");
- connect(job, SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
- connect(job, SIGNAL(progress(int, int)), this, SLOT(checkerProgress(int, int)));
+ m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
+ connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
+ connect(m_job.get(), SIGNAL(progress(int, int)), this, SLOT(checkerProgress(int, int)));
QLOG_DEBUG() << "Probing the following Java paths: ";
for(QString candidate : candidate_paths)
@@ -188,10 +188,10 @@ void JavaListLoadTask::executeTask()
auto candidate_checker = new JavaChecker();
candidate_checker->path = candidate;
- job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
+ m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
}
- job->start();
+ m_job->start();
}
void JavaListLoadTask::checkerProgress(int current, int total)
@@ -203,6 +203,7 @@ void JavaListLoadTask::checkerProgress(int current, int total)
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
{
QList<JavaVersionPtr> candidates;
+ m_job.reset();
QLOG_DEBUG() << "Found the following valid Java installations:";
for(JavaCheckResult result : results)
diff --git a/logic/lists/JavaVersionList.h b/logic/lists/JavaVersionList.h
index 879b2480..e6cc8e5f 100644
--- a/logic/lists/JavaVersionList.h
+++ b/logic/lists/JavaVersionList.h
@@ -90,6 +90,7 @@ public slots:
void checkerProgress(int current, int total);
protected:
+ std::shared_ptr<JavaCheckerJob> m_job;
JavaVersionList *m_list;
JavaVersion *m_currentRecommended;
};
diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp
index 523b81ac..91f86df0 100644
--- a/logic/lists/MinecraftVersionList.cpp
+++ b/logic/lists/MinecraftVersionList.cpp
@@ -139,7 +139,7 @@ MCVListLoadTask::~MCVListLoadTask()
void MCVListLoadTask::executeTask()
{
- setStatus("Loading instance version list...");
+ setStatus(tr("Loading instance version list..."));
auto worker = MMC->qnam();
vlistReply = worker->get(QNetworkRequest(QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json")));
connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded()));
diff --git a/logic/net/ForgeXzDownload.cpp b/logic/net/ForgeXzDownload.cpp
index 1771d304..359ad858 100644
--- a/logic/net/ForgeXzDownload.cpp
+++ b/logic/net/ForgeXzDownload.cpp
@@ -20,6 +20,7 @@
#include <QCryptographicHash>
#include <QFileInfo>
#include <QDateTime>
+#include <QDir>
#include "logger/QsLog.h"
ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : NetAction()
@@ -310,16 +311,51 @@ void ForgeXzDownload::decompressAndInstall()
m_pack200_xz_file.remove();
// revert pack200
- pack200_file.close();
- QString pack_name = pack200_file.fileName();
+ pack200_file.seek(0);
+ int handle_in = pack200_file.handle();
+ // FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects.
+ if(handle_in == -1)
+ {
+ QLOG_ERROR() << "Error reopening " << pack200_file.fileName();
+ failAndTryNextMirror();
+ return;
+ }
+ FILE * file_in = fdopen(handle_in,"r");
+ if(!file_in)
+ {
+ QLOG_ERROR() << "Error reopening " << pack200_file.fileName();
+ failAndTryNextMirror();
+ return;
+ }
+ QFile qfile_out(m_target_path);
+ if(!qfile_out.open(QIODevice::WriteOnly))
+ {
+ QLOG_ERROR() << "Error opening " << qfile_out.fileName();
+ failAndTryNextMirror();
+ return;
+ }
+ int handle_out = qfile_out.handle();
+ if(handle_out == -1)
+ {
+ QLOG_ERROR() << "Error opening " << qfile_out.fileName();
+ failAndTryNextMirror();
+ return;
+ }
+ FILE * file_out = fdopen(handle_out,"w");
+ if(!file_out)
+ {
+ QLOG_ERROR() << "Error opening " << qfile_out.fileName();
+ failAndTryNextMirror();
+ return;
+ }
try
{
- unpack_200(pack_name.toStdString(), m_target_path.toStdString());
+ unpack_200(file_in, file_out);
}
catch (std::runtime_error &err)
{
m_status = Job_Failed;
- QLOG_ERROR() << "Error unpacking " << pack_name.toUtf8() << " : " << err.what();
+ QLOG_ERROR() << "Error unpacking " << pack200_file.fileName() << " : " << err.what();
QFile f(m_target_path);
if (f.exists())
f.remove();
diff --git a/logic/net/MD5EtagDownload.cpp b/logic/net/MD5EtagDownload.cpp
index 435e854e..63583e8d 100644
--- a/logic/net/MD5EtagDownload.cpp
+++ b/logic/net/MD5EtagDownload.cpp
@@ -23,7 +23,6 @@ MD5EtagDownload::MD5EtagDownload(QUrl url, QString target_path) : NetAction()
{
m_url = url;
m_target_path = target_path;
- m_check_md5 = false;
m_status = Job_NotStarted;
}
@@ -34,22 +33,26 @@ void MD5EtagDownload::start()
// if there already is a file and md5 checking is in effect and it can be opened
if (m_output_file.exists() && m_output_file.open(QIODevice::ReadOnly))
{
- // check the md5 against the expected one
- QString hash =
+ // get the md5 of the local file.
+ m_local_md5 =
QCryptographicHash::hash(m_output_file.readAll(), QCryptographicHash::Md5)
.toHex()
.constData();
m_output_file.close();
- // skip this file if they match
- if (m_check_md5 && hash == m_expected_md5)
+ // if we are expecting some md5sum, compare it with the local one
+ if (!m_expected_md5.isEmpty())
{
- QLOG_INFO() << "Skipping " << m_url.toString() << ": md5 match.";
- emit succeeded(m_index_within_job);
- return;
+ // skip if they match
+ if(m_local_md5 == m_expected_md5)
+ {
+ QLOG_INFO() << "Skipping " << m_url.toString() << ": md5 match.";
+ emit succeeded(m_index_within_job);
+ return;
+ }
}
else
{
- m_expected_md5 = hash;
+ // no expected md5. we use the local md5sum as an ETag
}
}
if (!ensureFilePathExists(filename))
@@ -58,9 +61,18 @@ void MD5EtagDownload::start()
return;
}
- QLOG_INFO() << "Downloading " << m_url.toString() << " expecting " << m_expected_md5;
QNetworkRequest request(m_url);
- request.setRawHeader(QString("If-None-Match").toLatin1(), m_expected_md5.toLatin1());
+
+ QLOG_INFO() << "Downloading " << m_url.toString() << " got " << m_local_md5;
+
+ if(!m_local_md5.isEmpty())
+ {
+ QLOG_INFO() << "Got " << m_local_md5;
+ request.setRawHeader(QString("If-None-Match").toLatin1(), m_local_md5.toLatin1());
+ }
+ if(!m_expected_md5.isEmpty())
+ QLOG_INFO() << "Expecting " << m_expected_md5;
+
request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
// Go ahead and try to open the file.
@@ -107,7 +119,10 @@ void MD5EtagDownload::downloadFinished()
m_status = Job_Finished;
m_output_file.close();
+ // FIXME: compare with the real written data md5sum
+ // this is just an ETag
QLOG_INFO() << "Finished " << m_url.toString() << " got " << m_reply->rawHeader("ETag").constData();
+
m_reply.reset();
emit succeeded(m_index_within_job);
return;
@@ -116,6 +131,7 @@ void MD5EtagDownload::downloadFinished()
else
{
m_output_file.close();
+ m_output_file.remove();
m_reply.reset();
emit failed(m_index_within_job);
return;
diff --git a/logic/net/MD5EtagDownload.h b/logic/net/MD5EtagDownload.h
index 416ab9de..d5aed0ca 100644
--- a/logic/net/MD5EtagDownload.h
+++ b/logic/net/MD5EtagDownload.h
@@ -23,12 +23,10 @@ class MD5EtagDownload : public NetAction
{
Q_OBJECT
public:
- /// if true, check the md5sum against a provided md5sum
- /// also, if a file exists, perform an md5sum first and don't download only if they don't
- /// match
- bool m_check_md5;
- /// the expected md5 checksum
+ /// the expected md5 checksum. Only set from outside
QString m_expected_md5;
+ /// the md5 checksum of a file that already exists.
+ QString m_local_md5;
/// if saving to file, use the one specified in this string
QString m_target_path;
/// this is the output file, if any
diff --git a/logic/net/URLConstants.h b/logic/net/URLConstants.h
index dcd5c2b1..9579198d 100644
--- a/logic/net/URLConstants.h
+++ b/logic/net/URLConstants.h
@@ -29,4 +29,6 @@ const QString RESOURCE_BASE("resources.download.minecraft.net/");
const QString LIBRARY_BASE("libraries.minecraft.net/");
const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/");
const QString AUTH_BASE("authserver.mojang.com/");
+const QString FORGE_LEGACY_URL("http://files.minecraftforge.net/minecraftforge/json");
+const QString FORGE_GRADLE_URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/json");
}
diff --git a/logic/tasks/SequentialTask.cpp b/logic/tasks/SequentialTask.cpp
new file mode 100644
index 00000000..63025eee
--- /dev/null
+++ b/logic/tasks/SequentialTask.cpp
@@ -0,0 +1,77 @@
+#include "SequentialTask.h"
+
+SequentialTask::SequentialTask(QObject *parent) :
+ Task(parent), m_currentIndex(-1)
+{
+
+}
+
+QString SequentialTask::getStatus() const
+{
+ if (m_queue.isEmpty() || m_currentIndex >= m_queue.size())
+ {
+ return QString();
+ }
+ return m_queue.at(m_currentIndex)->getStatus();
+}
+
+void SequentialTask::getProgress(qint64 &current, qint64 &total)
+{
+ current = 0;
+ total = 0;
+ for (int i = 0; i < m_queue.size(); ++i)
+ {
+ qint64 subCurrent, subTotal;
+ m_queue.at(i)->getProgress(subCurrent, subTotal);
+ current += subCurrent;
+ total += subTotal;
+ }
+}
+
+void SequentialTask::addTask(std::shared_ptr<Task> task)
+{
+ m_queue.append(task);
+}
+
+void SequentialTask::executeTask()
+{
+ m_currentIndex = -1;
+ startNext();
+}
+
+void SequentialTask::startNext()
+{
+ if (m_currentIndex != -1)
+ {
+ std::shared_ptr<Task> previous = m_queue[m_currentIndex];
+ disconnect(previous.get(), 0, this, 0);
+ }
+ m_currentIndex++;
+ if (m_queue.isEmpty() || m_currentIndex >= m_queue.size())
+ {
+ emitSucceeded();
+ return;
+ }
+ std::shared_ptr<Task> next = m_queue[m_currentIndex];
+ connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString)));
+ connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString)));
+ connect(next.get(), SIGNAL(progress(qint64,qint64)), this, SLOT(subTaskProgress()));
+ connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext()));
+ next->start();
+ emit status(getStatus());
+}
+
+void SequentialTask::subTaskFailed(const QString &msg)
+{
+ emitFailed(msg);
+}
+void SequentialTask::subTaskStatus(const QString &msg)
+{
+ setStatus(msg);
+}
+void SequentialTask::subTaskProgress()
+{
+ qint64 current, total;
+ getProgress(current, total);
+ setProgress(100 * current / total);
+}
diff --git a/logic/tasks/SequentialTask.h b/logic/tasks/SequentialTask.h
new file mode 100644
index 00000000..7f046928
--- /dev/null
+++ b/logic/tasks/SequentialTask.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "Task.h"
+
+#include <QQueue>
+#include <memory>
+
+class SequentialTask : public Task
+{
+ Q_OBJECT
+public:
+ explicit SequentialTask(QObject *parent = 0);
+
+ virtual QString getStatus() const;
+ virtual void getProgress(qint64 &current, qint64 &total);
+
+ void addTask(std::shared_ptr<Task> task);
+
+protected:
+ void executeTask();
+
+private
+slots:
+ void startNext();
+ void subTaskFailed(const QString &msg);
+ void subTaskStatus(const QString &msg);
+ void subTaskProgress();
+
+private:
+ QQueue<std::shared_ptr<Task> > m_queue;
+ int m_currentIndex;
+};
diff --git a/logic/tasks/ThreadTask.cpp b/logic/tasks/ThreadTask.cpp
new file mode 100644
index 00000000..ddd1dee5
--- /dev/null
+++ b/logic/tasks/ThreadTask.cpp
@@ -0,0 +1,41 @@
+#include "ThreadTask.h"
+#include <QtConcurrentRun>
+ThreadTask::ThreadTask(Task * internal, QObject *parent) : Task(parent), m_internal(internal)
+{
+}
+
+void ThreadTask::start()
+{
+ connect(m_internal, SIGNAL(failed(QString)), SLOT(iternal_failed(QString)));
+ connect(m_internal, SIGNAL(progress(qint64,qint64)), SLOT(iternal_progress(qint64,qint64)));
+ connect(m_internal, SIGNAL(started()), SLOT(iternal_started()));
+ connect(m_internal, SIGNAL(status(QString)), SLOT(iternal_status(QString)));
+ connect(m_internal, SIGNAL(succeeded()), SLOT(iternal_succeeded()));
+ m_running = true;
+ QtConcurrent::run(m_internal, &Task::start);
+}
+
+void ThreadTask::iternal_failed(QString reason)
+{
+ emitFailed(reason);
+}
+
+void ThreadTask::iternal_progress(qint64 current, qint64 total)
+{
+ progress(current, total);
+}
+
+void ThreadTask::iternal_started()
+{
+ emit started();
+}
+
+void ThreadTask::iternal_status(QString status)
+{
+ setStatus(status);
+}
+
+void ThreadTask::iternal_succeeded()
+{
+ emitSucceeded();
+}
diff --git a/logic/tasks/ThreadTask.h b/logic/tasks/ThreadTask.h
new file mode 100644
index 00000000..718dbc91
--- /dev/null
+++ b/logic/tasks/ThreadTask.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "Task.h"
+
+class ThreadTask : public Task
+{
+ Q_OBJECT
+public:
+ explicit ThreadTask(Task * internal, QObject * parent = nullptr);
+
+protected:
+ void executeTask() {};
+
+public slots:
+ virtual void start();
+
+private slots:
+ void iternal_started();
+ void iternal_progress(qint64 current, qint64 total);
+ void iternal_succeeded();
+ void iternal_failed(QString reason);
+ void iternal_status(QString status);
+private:
+ Task * m_internal;
+}; \ No newline at end of file
diff --git a/logic/updater/DownloadUpdateTask.cpp b/logic/updater/DownloadUpdateTask.cpp
index d9aab826..6e0a92f0 100644
--- a/logic/updater/DownloadUpdateTask.cpp
+++ b/logic/updater/DownloadUpdateTask.cpp
@@ -26,9 +26,8 @@
#include <QDomDocument>
-
-DownloadUpdateTask::DownloadUpdateTask(QString repoUrl, int versionId, QObject* parent) :
- Task(parent)
+DownloadUpdateTask::DownloadUpdateTask(QString repoUrl, int versionId, QObject *parent)
+ : Task(parent)
{
m_cVersionId = MMC->version().build;
@@ -45,68 +44,69 @@ void DownloadUpdateTask::executeTask()
findCurrentVersionInfo();
}
-void DownloadUpdateTask::findCurrentVersionInfo()
+void DownloadUpdateTask::processChannels()
{
- setStatus(tr("Finding information about the current version."));
-
auto checker = MMC->updateChecker();
- // This runs after we've tried loading the channel list.
- // If the channel list doesn't need to be loaded, this will be called immediately.
- // If the channel list does need to be loaded, this will be called when it's done.
- auto processFunc = [this, &checker] () -> void
+ // Now, check the channel list again.
+ if (!checker->hasChannels())
{
- // Now, check the channel list again.
- if (checker->hasChannels())
- {
- // We still couldn't load the channel list. Give up. Call loadVersionInfo and return.
- QLOG_INFO() << "Reloading the channel list didn't work. Giving up.";
- loadVersionInfo();
- return;
- }
+ // We still couldn't load the channel list. Give up. Call loadVersionInfo and return.
+ QLOG_INFO() << "Reloading the channel list didn't work. Giving up.";
+ loadVersionInfo();
+ return;
+ }
- QList<UpdateChecker::ChannelListEntry> channels = checker->getChannelList();
- QString channelId = MMC->version().channel;
+ QList<UpdateChecker::ChannelListEntry> channels = checker->getChannelList();
+ QString channelId = MMC->version().channel;
- // Search through the channel list for a channel with the correct ID.
- for (auto channel : channels)
+ // Search through the channel list for a channel with the correct ID.
+ for (auto channel : channels)
+ {
+ if (channel.id == channelId)
{
- if (channel.id == channelId)
- {
- QLOG_INFO() << "Found matching channel.";
- m_cRepoUrl = channel.url;
- break;
- }
+ QLOG_INFO() << "Found matching channel.";
+ m_cRepoUrl = fixPathForTests(channel.url);
+ break;
}
+ }
- // Now that we've done that, load version info.
- loadVersionInfo();
- };
+ // Now that we've done that, load version info.
+ loadVersionInfo();
+}
+
+void DownloadUpdateTask::findCurrentVersionInfo()
+{
+ setStatus(tr("Finding information about the current version..."));
+
+ auto checker = MMC->updateChecker();
- if (checker->hasChannels())
+ if (!checker->hasChannels())
{
// Load the channel list and wait for it to finish loading.
QLOG_INFO() << "No channel list entries found. Will try reloading it.";
- QObject::connect(checker.get(), &UpdateChecker::channelListLoaded, processFunc);
+ QObject::connect(checker.get(), &UpdateChecker::channelListLoaded, this,
+ &DownloadUpdateTask::processChannels);
checker->updateChanList();
}
else
{
- processFunc();
+ processChannels();
}
}
void DownloadUpdateTask::loadVersionInfo()
{
- setStatus(tr("Loading version information."));
+ setStatus(tr("Loading version information..."));
// Create the net job for loading version info.
- NetJob* netJob = new NetJob("Version Info");
-
+ NetJob *netJob = new NetJob("Version Info");
+
// Find the index URL.
QUrl newIndexUrl = QUrl(m_nRepoUrl).resolved(QString::number(m_nVersionId) + ".json");
-
+ QLOG_DEBUG() << m_nRepoUrl << " turns into " << newIndexUrl;
+
// Add a net action to download the version info for the version we're updating to.
netJob->addNetAction(ByteArrayDownload::make(newIndexUrl));
@@ -115,10 +115,12 @@ void DownloadUpdateTask::loadVersionInfo()
{
QUrl cIndexUrl = QUrl(m_cRepoUrl).resolved(QString::number(m_cVersionId) + ".json");
netJob->addNetAction(ByteArrayDownload::make(cIndexUrl));
+ QLOG_DEBUG() << m_cRepoUrl << " turns into " << cIndexUrl;
}
// Connect slots so we know when it's done.
- QObject::connect(netJob, &NetJob::succeeded, this, &DownloadUpdateTask::vinfoDownloadFinished);
+ QObject::connect(netJob, &NetJob::succeeded, this,
+ &DownloadUpdateTask::vinfoDownloadFinished);
QObject::connect(netJob, &NetJob::failed, this, &DownloadUpdateTask::vinfoDownloadFailed);
// Store the NetJob in a class member. We don't want to lose it!
@@ -136,7 +138,8 @@ void DownloadUpdateTask::vinfoDownloadFinished()
void DownloadUpdateTask::vinfoDownloadFailed()
{
- // Something failed. We really need the second download (current version info), so parse downloads anyways as long as the first one succeeded.
+ // Something failed. We really need the second download (current version info), so parse
+ // downloads anyways as long as the first one succeeded.
if (m_vinfoNetJob->first()->m_status != Job_Failed)
{
parseDownloadedVersionInfo();
@@ -150,61 +153,73 @@ void DownloadUpdateTask::vinfoDownloadFailed()
void DownloadUpdateTask::parseDownloadedVersionInfo()
{
- setStatus(tr("Reading file lists."));
-
- parseVersionInfo(NEW_VERSION, &m_nVersionFileList);
+ setStatus(tr("Reading file list for new version..."));
+ QLOG_DEBUG() << "Reading file list for new version...";
+ QString error;
+ if (!parseVersionInfo(
+ std::dynamic_pointer_cast<ByteArrayDownload>(m_vinfoNetJob->first())->m_data,
+ &m_nVersionFileList, &error))
+ {
+ emitFailed(error);
+ return;
+ }
- // If there is a second entry in the network job's list, load it as the current version's info.
+ // If there is a second entry in the network job's list, load it as the current version's
+ // info.
if (m_vinfoNetJob->size() >= 2 && m_vinfoNetJob->operator[](1)->m_status != Job_Failed)
{
- parseVersionInfo(CURRENT_VERSION, &m_cVersionFileList);
+ setStatus(tr("Reading file list for current version..."));
+ QLOG_DEBUG() << "Reading file list for current version...";
+ QString error;
+ parseVersionInfo(
+ std::dynamic_pointer_cast<ByteArrayDownload>(m_vinfoNetJob->operator[](1))->m_data,
+ &m_cVersionFileList, &error);
}
// We don't need this any more.
m_vinfoNetJob.reset();
- // Now that we're done loading version info, we can move on to the next step. Process file lists and download files.
+ // Now that we're done loading version info, we can move on to the next step. Process file
+ // lists and download files.
processFileLists();
}
-void DownloadUpdateTask::parseVersionInfo(VersionInfoFileEnum vfile, VersionFileList* list)
+bool DownloadUpdateTask::parseVersionInfo(const QByteArray &data, VersionFileList *list,
+ QString *error)
{
- if (vfile == CURRENT_VERSION) setStatus(tr("Reading file list for current version."));
- else if (vfile == NEW_VERSION) setStatus(tr("Reading file list for new version."));
-
- QLOG_DEBUG() << "Reading file list for" << (vfile == NEW_VERSION ? "new" : "current") << "version.";
-
- QByteArray data;
- {
- ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(
- vfile == NEW_VERSION ? m_vinfoNetJob->first() : m_vinfoNetJob->operator[](1));
- data = dl->m_data;
- }
-
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
- QLOG_ERROR() << "Failed to parse version info JSON:" << jsonError.errorString() << "at" << jsonError.offset;
- return;
+ *error = QString("Failed to parse version info JSON: %1 at %2")
+ .arg(jsonError.errorString())
+ .arg(jsonError.offset);
+ QLOG_ERROR() << error;
+ return false;
}
QJsonObject json = jsonDoc.object();
+ QLOG_DEBUG() << data;
QLOG_DEBUG() << "Loading version info from JSON.";
QJsonArray filesArray = json.value("Files").toArray();
for (QJsonValue fileValue : filesArray)
{
QJsonObject fileObj = fileValue.toObject();
- VersionFileEntry file{
- fileObj.value("Path").toString(),
- fileObj.value("Perms").toVariant().toInt(),
- FileSourceList(),
- fileObj.value("MD5").toString(),
- };
+ QString file_path = fileObj.value("Path").toString();
+#ifdef Q_OS_MAC
+ // On OSX, the paths for the updater need to be fixed.
+ // basically, anything that isn't in the .app folder is ignored.
+ // everything else is changed so the code that processes the files actually finds
+ // them and puts the replacements in the right spots.
+ if (!fixPathForOSX(file_path))
+ continue;
+#endif
+ VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(),
+ FileSourceList(), fileObj.value("MD5").toString(), };
QLOG_DEBUG() << "File" << file.path << "with perms" << file.mode;
-
+
QJsonArray sourceArray = fileObj.value("Sources").toArray();
for (QJsonValue val : sourceArray)
{
@@ -213,11 +228,14 @@ void DownloadUpdateTask::parseVersionInfo(VersionInfoFileEnum vfile, VersionFile
QString type = sourceObj.value("SourceType").toString();
if (type == "http")
{
- file.sources.append(FileSource("http", sourceObj.value("Url").toString()));
+ file.sources.append(
+ FileSource("http", fixPathForTests(sourceObj.value("Url").toString())));
}
else if (type == "httpc")
{
- file.sources.append(FileSource("httpc", sourceObj.value("Url").toString(), sourceObj.value("CompressionType").toString()));
+ file.sources.append(
+ FileSource("httpc", fixPathForTests(sourceObj.value("Url").toString()),
+ sourceObj.value("CompressionType").toString()));
}
else
{
@@ -229,90 +247,185 @@ void DownloadUpdateTask::parseVersionInfo(VersionInfoFileEnum vfile, VersionFile
list->append(file);
}
+
+ return true;
}
void DownloadUpdateTask::processFileLists()
{
- setStatus(tr("Processing file lists. Figuring out how to install the update."));
+ // Create a network job for downloading files.
+ NetJob *netJob = new NetJob("Update Files");
+
+ if (!processFileLists(netJob, m_cVersionFileList, m_nVersionFileList, m_operationList))
+ {
+ emitFailed(tr("Failed to process update lists..."));
+ return;
+ }
+
+ // Add listeners to wait for the downloads to finish.
+ QObject::connect(netJob, &NetJob::succeeded, this,
+ &DownloadUpdateTask::fileDownloadFinished);
+ QObject::connect(netJob, &NetJob::progress, this,
+ &DownloadUpdateTask::fileDownloadProgressChanged);
+ QObject::connect(netJob, &NetJob::failed, this, &DownloadUpdateTask::fileDownloadFailed);
+
+ // Now start the download.
+ setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
+ QLOG_DEBUG() << "Begin downloading update files to" << m_updateFilesDir.path();
+ m_filesNetJob.reset(netJob);
+ netJob->start();
+
+ writeInstallScript(m_operationList, PathCombine(m_updateFilesDir.path(), "file_list.xml"));
+}
+
+bool
+DownloadUpdateTask::processFileLists(NetJob *job,
+ const DownloadUpdateTask::VersionFileList &currentVersion,
+ const DownloadUpdateTask::VersionFileList &newVersion,
+ DownloadUpdateTask::UpdateOperationList &ops)
+{
+ setStatus(tr("Processing file lists - figuring out how to install the update..."));
- // First, if we've loaded the current version's file list, we need to iterate through it and
+ // First, if we've loaded the current version's file list, we need to iterate through it and
// delete anything in the current one version's list that isn't in the new version's list.
- for (VersionFileEntry entry : m_cVersionFileList)
+ for (VersionFileEntry entry : currentVersion)
{
+ QFileInfo toDelete(entry.path);
+ if (!toDelete.exists())
+ {
+ QLOG_ERROR() << "Expected file " << toDelete.absoluteFilePath()
+ << " doesn't exist!";
+ QLOG_ERROR() << "CWD: " << QDir::currentPath();
+ }
bool keep = false;
- for (VersionFileEntry newEntry : m_nVersionFileList)
+
+ //
+ for (VersionFileEntry newEntry : newVersion)
{
if (newEntry.path == entry.path)
{
- QLOG_DEBUG() << "Not deleting" << entry.path << "because it is still present in the new version.";
+ QLOG_DEBUG() << "Not deleting" << entry.path
+ << "because it is still present in the new version.";
keep = true;
break;
}
}
+
// If the loop reaches the end and we didn't find a match, delete the file.
- if(!keep)
- m_operationList.append(UpdateOperation::DeleteOp(entry.path));
+ if (!keep)
+ {
+ QFileInfo toDelete(entry.path);
+ if (toDelete.exists())
+ ops.append(UpdateOperation::DeleteOp(entry.path));
+ }
}
- // Create a network job for downloading files.
- NetJob* netJob = new NetJob("Update Files");
-
// Next, check each file in MultiMC's folder and see if we need to update them.
- for (VersionFileEntry entry : m_nVersionFileList)
+ for (VersionFileEntry entry : newVersion)
{
- // TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a way to do this in the background.
+ // TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a
+ // way to do this in the background.
QString fileMD5;
QFile entryFile(entry.path);
- if (entryFile.open(QFile::ReadOnly))
+ QFileInfo entryInfo(entry.path);
+
+ bool needs_upgrade = false;
+ if (!entryFile.exists())
{
- QCryptographicHash hash(QCryptographicHash::Md5);
- hash.addData(entryFile.readAll());
- fileMD5 = hash.result().toHex();
+ needs_upgrade = true;
+ }
+ else
+ {
+ bool pass = true;
+ if (!entryInfo.isReadable())
+ {
+ QLOG_ERROR() << "File " << entry.path << " is not readable.";
+ pass = false;
+ }
+ if (!entryInfo.isWritable())
+ {
+ QLOG_ERROR() << "File " << entry.path << " is not writable.";
+ pass = false;
+ }
+ if (!entryFile.open(QFile::ReadOnly))
+ {
+ QLOG_ERROR() << "File " << entry.path << " cannot be opened for reading.";
+ pass = false;
+ }
+ if (!pass)
+ {
+ QLOG_ERROR() << "CWD: " << QDir::currentPath();
+ ops.clear();
+ return false;
+ }
}
- if (!entryFile.exists() || fileMD5.isEmpty() || fileMD5 != entry.md5)
+ QCryptographicHash hash(QCryptographicHash::Md5);
+ auto foo = entryFile.readAll();
+
+ hash.addData(foo);
+ fileMD5 = hash.result().toHex();
+ if ((fileMD5 != entry.md5))
{
- QLOG_DEBUG() << "Found file" << entry.path << "that needs updating.";
+ QLOG_DEBUG() << "MD5Sum does not match!";
+ QLOG_DEBUG() << "Expected:'" << entry.md5 << "'";
+ QLOG_DEBUG() << "Got: '" << fileMD5 << "'";
+ needs_upgrade = true;
+ }
+
+ // skip file. it doesn't need an upgrade.
+ if (!needs_upgrade)
+ {
+ QLOG_DEBUG() << "File" << entry.path << " does not need updating.";
+ continue;
+ }
- // Go through the sources list and find one to use.
- // TODO: Make a NetAction that takes a source list and tries each of them until one works. For now, we'll just use the first http one.
- for (FileSource source : entry.sources)
+ // yep. this file actually needs an upgrade. PROCEED.
+ QLOG_DEBUG() << "Found file" << entry.path << " that needs updating.";
+
+ // if it's the updater we want to treat it separately
+ bool isUpdater = entry.path.endsWith("updater") || entry.path.endsWith("updater.exe");
+
+ // Go through the sources list and find one to use.
+ // TODO: Make a NetAction that takes a source list and tries each of them until one
+ // works. For now, we'll just use the first http one.
+ for (FileSource source : entry.sources)
+ {
+ if (source.type == "http")
{
- if (source.type == "http")
- {
- QLOG_DEBUG() << "Will download" << entry.path << "from" << source.url;
+ QLOG_DEBUG() << "Will download" << entry.path << "from" << source.url;
- // Download it to updatedir/<filepath>-<md5> where filepath is the file's path with slashes replaced by underscores.
- QString dlPath = PathCombine(m_updateFilesDir.path(), QString(entry.path).replace("/", "_"));
+ // Download it to updatedir/<filepath>-<md5> where filepath is the file's
+ // path with slashes replaced by underscores.
+ QString dlPath =
+ PathCombine(m_updateFilesDir.path(), QString(entry.path).replace("/", "_"));
- // We need to download the file to the updatefiles folder and add a task to copy it to its install path.
+ if (isUpdater)
+ {
+ auto cache_entry = MMC->metacache()->resolveEntry("root", entry.path);
+ QLOG_DEBUG() << "Updater will be in " << cache_entry->getFullPath();
+ if(cache_entry->stale)
+ {
+ auto download = CacheDownload::make(QUrl(source.url), cache_entry);
+ job->addNetAction(download);
+ }
+ }
+ else
+ {
+ // We need to download the file to the updatefiles folder and add a task
+ // to copy it to its install path.
auto download = MD5EtagDownload::make(source.url, dlPath);
- download->m_check_md5 = true;
download->m_expected_md5 = entry.md5;
- netJob->addNetAction(download);
-
- // Now add a copy operation to our operations list to install the file.
- m_operationList.append(UpdateOperation::CopyOp(dlPath, entry.path, entry.mode));
+ job->addNetAction(download);
+ ops.append(UpdateOperation::CopyOp(dlPath, entry.path, entry.mode));
}
}
}
}
-
- // Add listeners to wait for the downloads to finish.
- QObject::connect(netJob, &NetJob::succeeded, this, &DownloadUpdateTask::fileDownloadFinished);
- QObject::connect(netJob, &NetJob::progress, this, &DownloadUpdateTask::fileDownloadProgressChanged);
- QObject::connect(netJob, &NetJob::failed, this, &DownloadUpdateTask::fileDownloadFailed);
-
- // Now start the download.
- setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
- QLOG_DEBUG() << "Begin downloading update files to" << m_updateFilesDir.path();
- m_filesNetJob.reset(netJob);
- netJob->start();
-
- writeInstallScript(m_operationList, PathCombine(m_updateFilesDir.path(), "file_list.xml"));
+ return true;
}
-void DownloadUpdateTask::writeInstallScript(UpdateOperationList& opsList, QString scriptFile)
+bool DownloadUpdateTask::writeInstallScript(UpdateOperationList &opsList, QString scriptFile)
{
// Build the base structure of the XML document.
QDomDocument doc;
@@ -334,36 +447,38 @@ void DownloadUpdateTask::writeInstallScript(UpdateOperationList& opsList, QStrin
switch (op.type)
{
- case UpdateOperation::OP_COPY:
- {
- // Install the file.
- QDomElement name = doc.createElement("source");
- QDomElement path = doc.createElement("dest");
- QDomElement mode = doc.createElement("mode");
- name.appendChild(doc.createTextNode(op.file));
- path.appendChild(doc.createTextNode(op.dest));
- // We need to add a 0 at the beginning here, because Qt doesn't convert to octal correctly.
- mode.appendChild(doc.createTextNode("0" + QString::number(op.mode, 8)));
- file.appendChild(name);
- file.appendChild(path);
- file.appendChild(mode);
- installFiles.appendChild(file);
- QLOG_DEBUG() << "Will install file" << op.file;
- }
- break;
+ case UpdateOperation::OP_COPY:
+ {
+ // Install the file.
+ QDomElement name = doc.createElement("source");
+ QDomElement path = doc.createElement("dest");
+ QDomElement mode = doc.createElement("mode");
+ name.appendChild(doc.createTextNode(op.file));
+ path.appendChild(doc.createTextNode(op.dest));
+ // We need to add a 0 at the beginning here, because Qt doesn't convert to octal
+ // correctly.
+ mode.appendChild(doc.createTextNode("0" + QString::number(op.mode, 8)));
+ file.appendChild(name);
+ file.appendChild(path);
+ file.appendChild(mode);
+ installFiles.appendChild(file);
+ QLOG_DEBUG() << "Will install file " << op.file << " to " << op.dest;
+ }
+ break;
- case UpdateOperation::OP_DELETE:
- {
- // Delete the file.
- file.appendChild(doc.createTextNode(op.file));
- removeFiles.appendChild(file);
- QLOG_DEBUG() << "Will remove file" << op.file;
- }
- break;
+ case UpdateOperation::OP_DELETE:
+ {
+ // Delete the file.
+ file.appendChild(doc.createTextNode(op.file));
+ removeFiles.appendChild(file);
+ QLOG_DEBUG() << "Will remove file" << op.file;
+ }
+ break;
- default:
- QLOG_WARN() << "Can't write update operation of type" << op.type << "to file. Not implemented.";
- continue;
+ default:
+ QLOG_WARN() << "Can't write update operation of type" << op.type
+ << "to file. Not implemented.";
+ continue;
}
}
@@ -377,6 +492,36 @@ void DownloadUpdateTask::writeInstallScript(UpdateOperationList& opsList, QStrin
else
{
emitFailed(tr("Failed to write update script file."));
+ return false;
+ }
+
+ return true;
+}
+
+QString DownloadUpdateTask::fixPathForTests(const QString &path)
+{
+ if (path.startsWith("$PWD"))
+ {
+ QString foo = path;
+ foo.replace("$PWD", qApp->applicationDirPath());
+ return QUrl::fromLocalFile(foo).toString(QUrl::FullyEncoded);
+ }
+ return path;
+}
+
+bool DownloadUpdateTask::fixPathForOSX(QString &path)
+{
+ if (path.startsWith("MultiMC.app/"))
+ {
+ // remove the prefix and add a new, more appropriate one.
+ path.remove(0, 12);
+ path = QString("../../") + path;
+ return true;
+ }
+ else
+ {
+ QLOG_ERROR() << "Update path not within .app: " << path;
+ return false;
}
}
@@ -394,11 +539,10 @@ void DownloadUpdateTask::fileDownloadFailed()
void DownloadUpdateTask::fileDownloadProgressChanged(qint64 current, qint64 total)
{
- setProgress((int)(((float)current / (float)total)*100));
+ setProgress((int)(((float)current / (float)total) * 100));
}
QString DownloadUpdateTask::updateFilesDir()
{
return m_updateFilesDir.path();
}
-
diff --git a/logic/updater/DownloadUpdateTask.h b/logic/updater/DownloadUpdateTask.h
index f5b23d12..b1d14846 100644
--- a/logic/updater/DownloadUpdateTask.h
+++ b/logic/updater/DownloadUpdateTask.h
@@ -34,7 +34,8 @@ public:
*/
QString updateFilesDir();
-protected:
+public:
+
// TODO: We should probably put these data structures into a separate header...
/*!
@@ -53,7 +54,6 @@ protected:
QString url;
QString compressionType;
};
-
typedef QList<FileSource> FileSourceList;
/*!
@@ -66,10 +66,8 @@ protected:
FileSourceList sources;
QString md5;
};
-
typedef QList<VersionFileEntry> VersionFileList;
-
/*!
* Structure that describes an operation to perform when installing updates.
*/
@@ -100,9 +98,12 @@ protected:
// Yeah yeah, polymorphism blah blah inheritance, blah blah object oriented. I'm lazy, OK?
};
-
typedef QList<UpdateOperation> UpdateOperationList;
+protected:
+ friend class DownloadUpdateTaskTest;
+
+
/*!
* Used for arguments to parseVersionInfo and friends to specify which version info file to parse.
*/
@@ -120,6 +121,13 @@ protected:
virtual void findCurrentVersionInfo();
/*!
+ * This runs after we've tried loading the channel list.
+ * If the channel list doesn't need to be loaded, this will be called immediately.
+ * If the channel list does need to be loaded, this will be called when it's done.
+ */
+ void processChannels();
+
+ /*!
* Downloads the version info files from the repository.
* The files for both the current build, and the build that we're updating to need to be downloaded.
* If the current version's info file can't be found, MultiMC will not delete files that
@@ -142,20 +150,25 @@ protected:
/*!
* Loads the file list from the given version info JSON object into the given list.
*/
- virtual void parseVersionInfo(VersionInfoFileEnum vfile, VersionFileList* list);
+ virtual bool parseVersionInfo(const QByteArray &data, VersionFileList* list, QString *error);
/*!
* Takes a list of file entries for the current version's files and the new version's files
* and populates the downloadList and operationList with information about how to download and install the update.
*/
+ virtual bool processFileLists(NetJob *job, const VersionFileList &currentVersion, const VersionFileList &newVersion, UpdateOperationList &ops);
+
+ /*!
+ * Calls \see processFileLists to populate the \see m_operationList and a NetJob, and then executes
+ * the NetJob to fetch all needed files
+ */
virtual void processFileLists();
/*!
* Takes the operations list and writes an install script for the updater to the update files directory.
*/
- virtual void writeInstallScript(UpdateOperationList& opsList, QString scriptFile);
+ virtual bool writeInstallScript(UpdateOperationList& opsList, QString scriptFile);
- VersionFileList m_downloadList;
UpdateOperationList m_operationList;
VersionFileList m_nVersionFileList;
@@ -181,6 +194,26 @@ protected:
*/
QTemporaryDir m_updateFilesDir;
+ /*!
+ * Filters paths
+ * Path of the format $PWD/path, it is converted to a file:///$PWD/ URL
+ */
+ static QString fixPathForTests(const QString &path);
+
+ /*!
+ * Filters paths
+ * This fixes destination paths for OSX.
+ * The updater runs in MultiMC.app/Contents/MacOs by default
+ * The destination paths are such as this: MultiMC.app/blah/blah
+ *
+ * Therefore we chop off the 'MultiMC.app' prefix and prepend ../..
+ *
+ * Returns false if the path couldn't be fixed (is invalid)
+ *
+ * Has no effect on systems that aren't OSX
+ */
+ static bool fixPathForOSX(QString &path);
+
protected slots:
void vinfoDownloadFinished();
void vinfoDownloadFailed();
diff --git a/logic/updater/UpdateChecker.cpp b/logic/updater/UpdateChecker.cpp
index 5ff1898e..d0795c0d 100644
--- a/logic/updater/UpdateChecker.cpp
+++ b/logic/updater/UpdateChecker.cpp
@@ -44,17 +44,19 @@ QList<UpdateChecker::ChannelListEntry> UpdateChecker::getChannelList() const
bool UpdateChecker::hasChannels() const
{
- return m_channels.isEmpty();
+ return !m_channels.isEmpty();
}
-void UpdateChecker::checkForUpdate()
+void UpdateChecker::checkForUpdate(bool notifyNoUpdate)
{
QLOG_DEBUG() << "Checking for updates.";
- // If the channel list hasn't loaded yet, load it and defer checking for updates until later.
+ // If the channel list hasn't loaded yet, load it and defer checking for updates until
+ // later.
if (!m_chanListLoaded)
{
- QLOG_DEBUG() << "Channel list isn't loaded yet. Loading channel list and deferring update check.";
+ QLOG_DEBUG() << "Channel list isn't loaded yet. Loading channel list and deferring "
+ "update check.";
m_checkUpdateWaiting = true;
updateChanList();
return;
@@ -72,7 +74,8 @@ void UpdateChecker::checkForUpdate()
// TODO: Allow user to select channels. For now, we'll just use the current channel.
QString updateChannel = m_currentChannel;
- // Find the desired channel within the channel list and get its repo URL. If if cannot be found, error.
+ // Find the desired channel within the channel list and get its repo URL. If if cannot be
+ // found, error.
m_repoUrl = "";
for (ChannelListEntry entry : m_channels)
{
@@ -91,20 +94,22 @@ void UpdateChecker::checkForUpdate()
auto job = new NetJob("GoUpdate Repository Index");
job->addNetAction(ByteArrayDownload::make(indexUrl));
- connect(job, SIGNAL(succeeded()), SLOT(updateCheckFinished()));
+ connect(job, &NetJob::succeeded, [this, notifyNoUpdate]()
+ { updateCheckFinished(notifyNoUpdate); });
connect(job, SIGNAL(failed()), SLOT(updateCheckFailed()));
indexJob.reset(job);
job->start();
}
-void UpdateChecker::updateCheckFinished()
+void UpdateChecker::updateCheckFinished(bool notifyNoUpdate)
{
QLOG_DEBUG() << "Finished downloading repo index. Checking for new versions.";
QJsonParseError jsonError;
QByteArray data;
{
- ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(indexJob->first());
+ ByteArrayDownloadPtr dl =
+ std::dynamic_pointer_cast<ByteArrayDownload>(indexJob->first());
data = dl->m_data;
indexJob.reset();
}
@@ -112,7 +117,8 @@ void UpdateChecker::updateCheckFinished()
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isObject())
{
- QLOG_ERROR() << "Failed to parse GoUpdate repository index. JSON error" << jsonError.errorString() << "at offset" << jsonError.offset;
+ QLOG_ERROR() << "Failed to parse GoUpdate repository index. JSON error"
+ << jsonError.errorString() << "at offset" << jsonError.offset;
return;
}
@@ -122,7 +128,8 @@ void UpdateChecker::updateCheckFinished()
int apiVersion = object.value("ApiVersion").toVariant().toInt(&success);
if (apiVersion != API_VERSION || !success)
{
- QLOG_ERROR() << "Failed to check for updates. API version mismatch. We're using" << API_VERSION << "server has" << apiVersion;
+ QLOG_ERROR() << "Failed to check for updates. API version mismatch. We're using"
+ << API_VERSION << "server has" << apiVersion;
return;
}
@@ -132,19 +139,27 @@ void UpdateChecker::updateCheckFinished()
for (QJsonValue versionVal : versions)
{
QJsonObject version = versionVal.toObject();
- if (newestVersion.value("Id").toVariant().toInt() < version.value("Id").toVariant().toInt())
+ if (newestVersion.value("Id").toVariant().toInt() <
+ version.value("Id").toVariant().toInt())
{
- QLOG_DEBUG() << "Found newer version with ID" << version.value("Id").toVariant().toInt();
+ QLOG_DEBUG() << "Found newer version with ID"
+ << version.value("Id").toVariant().toInt();
newestVersion = version;
}
}
- // We've got the version with the greatest ID number. Now compare it to our current build number and update if they're different.
+ // We've got the version with the greatest ID number. Now compare it to our current build
+ // number and update if they're different.
int newBuildNumber = newestVersion.value("Id").toVariant().toInt();
if (newBuildNumber != MMC->version().build)
{
// Update!
- emit updateAvailable(m_repoUrl, newestVersion.value("Name").toVariant().toString(), newBuildNumber);
+ emit updateAvailable(m_repoUrl, newestVersion.value("Name").toVariant().toString(),
+ newBuildNumber);
+ }
+ else if (notifyNoUpdate)
+ {
+ emit noUpdateFound();
}
m_updateChecking = false;
@@ -163,12 +178,13 @@ void UpdateChecker::updateChanList()
if (m_channelListUrl.isEmpty())
{
QLOG_ERROR() << "Failed to update channel list. No channel list URL set."
- << "If you'd like to use MultiMC's update system, please pass the channel list URL to CMake at compile time.";
+ << "If you'd like to use MultiMC's update system, please pass the channel "
+ "list URL to CMake at compile time.";
return;
}
m_chanListLoading = true;
- NetJob* job = new NetJob("Update System Channel List");
+ NetJob *job = new NetJob("Update System Channel List");
job->addNetAction(ByteArrayDownload::make(QUrl(m_channelListUrl)));
QObject::connect(job, &NetJob::succeeded, this, &UpdateChecker::chanListDownloadFinished);
QObject::connect(job, &NetJob::failed, this, &UpdateChecker::chanListDownloadFailed);
@@ -180,7 +196,8 @@ void UpdateChecker::chanListDownloadFinished()
{
QByteArray data;
{
- ByteArrayDownloadPtr dl = std::dynamic_pointer_cast<ByteArrayDownload>(chanListJob->first());
+ ByteArrayDownloadPtr dl =
+ std::dynamic_pointer_cast<ByteArrayDownload>(chanListJob->first());
data = dl->m_data;
chanListJob.reset();
}
@@ -190,17 +207,20 @@ void UpdateChecker::chanListDownloadFinished()
if (jsonError.error != QJsonParseError::NoError)
{
// TODO: Report errors to the user.
- QLOG_ERROR() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at" << jsonError.offset;
+ QLOG_ERROR() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at"
+ << jsonError.offset;
return;
}
-
+
QJsonObject object = jsonDoc.object();
bool success = false;
int formatVersion = object.value("format_version").toVariant().toInt(&success);
if (formatVersion != CHANLIST_FORMAT || !success)
{
- QLOG_ERROR() << "Failed to check for updates. Channel list format version mismatch. We're using" << CHANLIST_FORMAT << "server has" << formatVersion;
+ QLOG_ERROR()
+ << "Failed to check for updates. Channel list format version mismatch. We're using"
+ << CHANLIST_FORMAT << "server has" << formatVersion;
return;
}
@@ -210,12 +230,10 @@ void UpdateChecker::chanListDownloadFinished()
for (QJsonValue chanVal : channelArray)
{
QJsonObject channelObj = chanVal.toObject();
- ChannelListEntry entry{
- channelObj.value("id").toVariant().toString(),
- channelObj.value("name").toVariant().toString(),
- channelObj.value("description").toVariant().toString(),
- channelObj.value("url").toVariant().toString()
- };
+ ChannelListEntry entry{channelObj.value("id").toVariant().toString(),
+ channelObj.value("name").toVariant().toString(),
+ channelObj.value("description").toVariant().toString(),
+ channelObj.value("url").toVariant().toString()};
if (entry.id.isEmpty() || entry.name.isEmpty() || entry.url.isEmpty())
{
QLOG_ERROR() << "Channel list entry with empty ID, name, or URL. Skipping.";
@@ -233,7 +251,7 @@ void UpdateChecker::chanListDownloadFinished()
// If we're waiting to check for updates, do that now.
if (m_checkUpdateWaiting)
- checkForUpdate();
+ checkForUpdate(false);
emit channelListLoaded();
}
@@ -244,4 +262,3 @@ void UpdateChecker::chanListDownloadFailed()
QLOG_ERROR() << "Failed to download channel list.";
emit channelListLoaded();
}
-
diff --git a/logic/updater/UpdateChecker.h b/logic/updater/UpdateChecker.h
index 59fb8e47..a47e8903 100644
--- a/logic/updater/UpdateChecker.h
+++ b/logic/updater/UpdateChecker.h
@@ -25,7 +25,10 @@ class UpdateChecker : public QObject
public:
UpdateChecker();
- void checkForUpdate();
+ void checkForUpdate(bool notifyNoUpdate);
+
+ void setCurrentChannel(const QString &channel) { m_currentChannel = channel; }
+ void setChannelListUrl(const QString &url) { m_channelListUrl = url; }
/*!
* Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake).
@@ -62,14 +65,18 @@ signals:
//! Signal emitted when the channel list finishes loading or fails to load.
void channelListLoaded();
+ void noUpdateFound();
+
private slots:
- void updateCheckFinished();
+ void updateCheckFinished(bool notifyNoUpdate);
void updateCheckFailed();
void chanListDownloadFinished();
void chanListDownloadFailed();
private:
+ friend class UpdateCheckerTest;
+
NetJobPtr indexJob;
NetJobPtr chanListJob;