summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MultiMC.cpp4
-rw-r--r--depends/util/include/modutils.h31
-rw-r--r--depends/util/src/modutils.cpp174
-rw-r--r--gui/MainWindow.cpp7
-rw-r--r--gui/MainWindow.ui2
-rw-r--r--gui/dialogs/ModEditDialogCommon.cpp4
-rw-r--r--gui/dialogs/NewInstanceDialog.cpp9
-rw-r--r--gui/dialogs/NewInstanceDialog.h1
-rw-r--r--gui/groupview/InstanceDelegate.cpp16
-rw-r--r--gui/groupview/InstanceDelegate.h2
-rw-r--r--logic/BaseInstance.cpp34
-rw-r--r--logic/BaseInstance.h22
-rw-r--r--logic/BaseInstance_p.h2
-rw-r--r--logic/BaseVersion.h1
-rw-r--r--logic/InstanceFactory.cpp18
-rw-r--r--logic/InstanceFactory.h2
-rw-r--r--logic/LegacyFTBInstance.cpp2
-rw-r--r--logic/LegacyInstance.cpp2
-rw-r--r--logic/MMCJson.cpp65
-rw-r--r--logic/MMCJson.h50
-rw-r--r--logic/OneSixFTBInstance.cpp2
-rw-r--r--logic/OneSixInstance.cpp11
-rw-r--r--logic/OneSixInstance.h7
-rw-r--r--logic/forge/ForgeInstaller.h4
-rw-r--r--logic/forge/ForgeVersion.h2
-rw-r--r--logic/java/JavaCheckerJob.h12
-rw-r--r--logic/liteloader/LiteLoaderInstaller.h5
-rw-r--r--logic/liteloader/LiteLoaderVersionList.h2
-rw-r--r--logic/minecraft/InstanceVersion.cpp8
-rw-r--r--logic/minecraft/InstanceVersion.h6
-rw-r--r--logic/minecraft/MinecraftVersionList.cpp13
-rw-r--r--logic/minecraft/OneSixLibrary.h1
-rw-r--r--logic/minecraft/VersionBuilder.cpp5
-rw-r--r--logic/minecraft/VersionFile.cpp10
-rw-r--r--logic/net/ByteArrayDownload.cpp26
-rw-r--r--logic/net/ByteArrayDownload.h4
-rw-r--r--logic/net/CacheDownload.cpp24
-rw-r--r--logic/net/CacheDownload.h2
-rw-r--r--logic/net/NetAction.h3
-rw-r--r--logic/net/NetJob.h20
-rw-r--r--logic/tasks/ProgressProvider.h4
-rw-r--r--logic/tasks/SequentialTask.cpp45
-rw-r--r--logic/tasks/SequentialTask.h5
-rw-r--r--logic/tasks/Task.cpp14
-rw-r--r--logic/tasks/Task.h6
-rw-r--r--logic/tools/BaseExternalTool.h2
-rw-r--r--logic/tools/JProfiler.cpp2
-rw-r--r--logic/tools/JProfiler.h2
-rw-r--r--logic/tools/JVisualVM.cpp2
-rw-r--r--logic/tools/JVisualVM.h2
-rw-r--r--logic/tools/MCEditTool.cpp2
-rw-r--r--logic/tools/MCEditTool.h2
-rw-r--r--resources/instances/instances.qrc3
-rw-r--r--resources/instances/updateavailable.pngbin0 -> 2480 bytes
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/TestUtil.h35
-rw-r--r--tests/tst_modutils.cpp153
57 files changed, 569 insertions, 326 deletions
diff --git a/MultiMC.cpp b/MultiMC.cpp
index 94be69f8..d33fb5db 100644
--- a/MultiMC.cpp
+++ b/MultiMC.cpp
@@ -251,12 +251,12 @@ MultiMC::MultiMC(int &argc, char **argv, bool root_override) : QApplication(argc
std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory()));
for (auto profiler : m_profilers.values())
{
- profiler->registerSettings(m_settings.get());
+ profiler->registerSettings(m_settings);
}
m_tools.insert("mcedit", std::shared_ptr<BaseDetachedToolFactory>(new MCEditFactory()));
for (auto tool : m_tools.values())
{
- tool->registerSettings(m_settings.get());
+ tool->registerSettings(m_settings);
}
// launch instance, if that's what should be done
diff --git a/depends/util/include/modutils.h b/depends/util/include/modutils.h
index e04db66f..1fecd4d5 100644
--- a/depends/util/include/modutils.h
+++ b/depends/util/include/modutils.h
@@ -1,6 +1,8 @@
#pragma once
#include <QString>
+#include <QList>
+
#include "libutil_config.h"
class QUrl;
@@ -10,10 +12,12 @@ namespace Util
struct Version
{
Version(const QString &str);
+ Version() {}
bool operator<(const Version &other) const;
bool operator<=(const Version &other) const;
bool operator>(const Version &other) const;
+ bool operator>=(const Version &other) const;
bool operator==(const Version &other) const;
bool operator!=(const Version &other) const;
@@ -24,9 +28,34 @@ struct Version
private:
QString m_string;
+ struct Section
+ {
+ explicit Section(const QString &str, const int num) : numValid(true), number(num), string(str) {}
+ explicit Section(const QString &str) : numValid(false), string(str) {}
+ explicit Section() {}
+ bool numValid;
+ int number;
+ QString string;
+
+ inline bool operator!=(const Section &other) const
+ {
+ return (numValid && other.numValid) ? (number != other.number) : (string != other.string);
+ }
+ inline bool operator<(const Section &other) const
+ {
+ return (numValid && other.numValid) ? (number < other.number) : (string < other.string);
+ }
+ inline bool operator>(const Section &other) const
+ {
+ return (numValid && other.numValid) ? (number > other.number) : (string > other.string);
+ }
+ };
+ QList<Section> m_sections;
+
+ void parse();
};
-LIBUTIL_EXPORT QUrl expandQMURL(const QString &in);
LIBUTIL_EXPORT bool versionIsInInterval(const QString &version, const QString &interval);
+LIBUTIL_EXPORT bool versionIsInInterval(const Version &version, const QString &interval);
}
diff --git a/depends/util/src/modutils.cpp b/depends/util/src/modutils.cpp
index 44a04b72..67c09dec 100644
--- a/depends/util/src/modutils.cpp
+++ b/depends/util/src/modutils.cpp
@@ -7,42 +7,20 @@
Util::Version::Version(const QString &str) : m_string(str)
{
+ parse();
}
bool Util::Version::operator<(const Version &other) const
{
- QStringList parts1 = m_string.split('.');
- QStringList parts2 = other.m_string.split('.');
-
- while (!parts1.isEmpty() && !parts2.isEmpty())
+ const int size = qMax(m_sections.size(), other.m_sections.size());
+ for (int i = 0; i < size; ++i)
{
- QString part1 = parts1.isEmpty() ? "0" : parts1.takeFirst();
- QString part2 = parts2.isEmpty() ? "0" : parts2.takeFirst();
- bool ok1 = false;
- bool ok2 = false;
- int int1 = part1.toInt(&ok1);
- int int2 = part2.toInt(&ok2);
- if (ok1 && ok2)
- {
- if (int1 == int2)
- {
- continue;
- }
- else
- {
- return int1 < int2;
- }
- }
- else
+ const Section sec1 = (i >= m_sections.size()) ? Section("0", 0) : m_sections.at(i);
+ const Section sec2 =
+ (i >= other.m_sections.size()) ? Section("0", 0) : other.m_sections.at(i);
+ if (sec1 != sec2)
{
- if (part1 == part2)
- {
- continue;
- }
- else
- {
- return part1 < part2;
- }
+ return sec1 < sec2;
}
}
@@ -54,77 +32,35 @@ bool Util::Version::operator<=(const Util::Version &other) const
}
bool Util::Version::operator>(const Version &other) const
{
- QStringList parts1 = m_string.split('.');
- QStringList parts2 = other.m_string.split('.');
-
- while (!parts1.isEmpty() && !parts2.isEmpty())
+ const int size = qMax(m_sections.size(), other.m_sections.size());
+ for (int i = 0; i < size; ++i)
{
- QString part1 = parts1.isEmpty() ? "0" : parts1.takeFirst();
- QString part2 = parts2.isEmpty() ? "0" : parts2.takeFirst();
- bool ok1 = false;
- bool ok2 = false;
- int int1 = part1.toInt(&ok1);
- int int2 = part2.toInt(&ok2);
- if (ok1 && ok2)
+ const Section sec1 = (i >= m_sections.size()) ? Section("0", 0) : m_sections.at(i);
+ const Section sec2 =
+ (i >= other.m_sections.size()) ? Section("0", 0) : other.m_sections.at(i);
+ if (sec1 != sec2)
{
- if (int1 == int2)
- {
- continue;
- }
- else
- {
- return int1 > int2;
- }
- }
- else
- {
- if (part1 == part2)
- {
- continue;
- }
- else
- {
- return part1 > part2;
- }
+ return sec1 > sec2;
}
}
return false;
}
+bool Util::Version::operator>=(const Version &other) const
+{
+ return *this > other || *this == other;
+}
bool Util::Version::operator==(const Version &other) const
{
- QStringList parts1 = m_string.split('.');
- QStringList parts2 = other.m_string.split('.');
-
- while (!parts1.isEmpty() && !parts2.isEmpty())
+ const int size = qMax(m_sections.size(), other.m_sections.size());
+ for (int i = 0; i < size; ++i)
{
- QString part1 = parts1.isEmpty() ? "0" : parts1.takeFirst();
- QString part2 = parts2.isEmpty() ? "0" : parts2.takeFirst();
- bool ok1 = false;
- bool ok2 = false;
- int int1 = part1.toInt(&ok1);
- int int2 = part2.toInt(&ok2);
- if (ok1 && ok2)
+ const Section sec1 = (i >= m_sections.size()) ? Section("0", 0) : m_sections.at(i);
+ const Section sec2 =
+ (i >= other.m_sections.size()) ? Section("0", 0) : other.m_sections.at(i);
+ if (sec1 != sec2)
{
- if (int1 == int2)
- {
- continue;
- }
- else
- {
- return false;
- }
- }
- else
- {
- if (part1 == part2)
- {
- continue;
- }
- else
- {
- return false;
- }
+ return false;
}
}
@@ -135,45 +71,41 @@ bool Util::Version::operator!=(const Version &other) const
return !operator==(other);
}
-QUrl Util::expandQMURL(const QString &in)
+void Util::Version::parse()
{
- QUrl inUrl(in);
- if (inUrl.scheme() == "github")
- {
- // needed because QUrl makes the host all lower cases
- const QString repo = in.mid(in.indexOf(inUrl.host(), 0, Qt::CaseInsensitive), inUrl.host().size());
- QUrl out;
- out.setScheme("https");
- out.setHost("raw.github.com");
- out.setPath(QString("/%1/%2/%3%4")
- .arg(inUrl.userInfo(), repo,
- inUrl.fragment().isEmpty() ? "master" : inUrl.fragment(), inUrl.path()));
- return out;
- }
- else if (inUrl.scheme() == "mcf")
- {
- QUrl out;
- out.setScheme("http");
- out.setHost("www.minecraftforum.net");
- out.setPath(QString("/topic/%1-").arg(inUrl.path()));
- return out;
- }
- else
+ m_sections.clear();
+
+ QStringList parts = m_string.split('.');
+
+ for (const auto part : parts)
{
- return in;
+ bool ok = false;
+ int num = part.toInt(&ok);
+ if (ok)
+ {
+ m_sections.append(Section(part, num));
+ }
+ else
+ {
+ m_sections.append(Section(part));
+ }
}
}
bool Util::versionIsInInterval(const QString &version, const QString &interval)
{
- if (interval.isEmpty() || version == interval)
+ return versionIsInInterval(Util::Version(version), interval);
+}
+bool Util::versionIsInInterval(const Version &version, const QString &interval)
+{
+ if (interval.isEmpty() || version.toString() == interval)
{
return true;
}
// Interval notation is used
QRegularExpression exp(
- "(?<start>[\\[\\]\\(\\)])(?<bottom>.*?)(,(?<top>.*?))?(?<end>[\\[\\]\\(\\)])");
+ "(?<start>[\\[\\]\\(\\)])(?<bottom>.*?)(,(?<top>.*?))?(?<end>[\\[\\]\\(\\)]),?");
QRegularExpressionMatch match = exp.match(interval);
if (match.hasMatch())
{
@@ -185,11 +117,12 @@ bool Util::versionIsInInterval(const QString &version, const QString &interval)
// check if in range (bottom)
if (!bottom.isEmpty())
{
- if ((start == '[') && !(version >= bottom))
+ const auto bottomVersion = Util::Version(bottom);
+ if ((start == '[') && !(version >= bottomVersion))
{
return false;
}
- else if ((start == '(') && !(version > bottom))
+ else if ((start == '(') && !(version > bottomVersion))
{
return false;
}
@@ -198,11 +131,12 @@ bool Util::versionIsInInterval(const QString &version, const QString &interval)
// check if in range (top)
if (!top.isEmpty())
{
- if ((end == ']') && !(version <= top))
+ const auto topVersion = Util::Version(top);
+ if ((end == ']') && !(version <= topVersion))
{
return false;
}
- else if ((end == ')') && !(version < top))
+ else if ((end == ')') && !(version < topVersion))
{
return false;
}
diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp
index d3dc8f6e..a5bffcc4 100644
--- a/gui/MainWindow.cpp
+++ b/gui/MainWindow.cpp
@@ -65,9 +65,9 @@
#include "gui/pages/global/MultiMCPage.h"
#include "gui/pages/global/ExternalToolsPage.h"
#include "gui/pages/global/AccountListPage.h"
-#include "pages/global/ProxyPage.h"
-#include "pages/global/JavaPage.h"
-#include "pages/global/MinecraftPage.h"
+#include "gui/pages/global/ProxyPage.h"
+#include "gui/pages/global/JavaPage.h"
+#include "gui/pages/global/MinecraftPage.h"
#include "gui/ConsoleWindow.h"
#include "pagedialog/PageDialog.h"
@@ -91,6 +91,7 @@
#include "logic/net/NetJob.h"
#include "logic/BaseInstance.h"
+#include "logic/OneSixInstance.h"
#include "logic/InstanceFactory.h"
#include "logic/MinecraftProcess.h"
#include "logic/OneSixUpdate.h"
diff --git a/gui/MainWindow.ui b/gui/MainWindow.ui
index 03a07ce5..a3edf6bd 100644
--- a/gui/MainWindow.ui
+++ b/gui/MainWindow.ui
@@ -387,7 +387,7 @@
</action>
<action name="actionEditInstance">
<property name="text">
- <string>Edit Mods</string>
+ <string>Edit Instance</string>
</property>
<property name="iconText">
<string>Edit Instance</string>
diff --git a/gui/dialogs/ModEditDialogCommon.cpp b/gui/dialogs/ModEditDialogCommon.cpp
index 35942374..4a2115d8 100644
--- a/gui/dialogs/ModEditDialogCommon.cpp
+++ b/gui/dialogs/ModEditDialogCommon.cpp
@@ -4,7 +4,7 @@
bool lastfirst(QModelIndexList &list, int &first, int &last)
{
- if (!list.size())
+ if (list.isEmpty())
return false;
first = last = list[0].row();
for (auto item : list)
@@ -37,4 +37,4 @@ void showWebsiteForMod(QWidget *parentDlg, Mod &m)
QObject::tr("The mod author didn't provide a website link for this mod."),
QMessageBox::Warning);
}
-} \ No newline at end of file
+}
diff --git a/gui/dialogs/NewInstanceDialog.cpp b/gui/dialogs/NewInstanceDialog.cpp
index 41ae329c..80cbbeb2 100644
--- a/gui/dialogs/NewInstanceDialog.cpp
+++ b/gui/dialogs/NewInstanceDialog.cpp
@@ -38,15 +38,6 @@ NewInstanceDialog::NewInstanceDialog(QWidget *parent)
ui->setupUi(this);
resize(minimumSizeHint());
layout()->setSizeConstraint(QLayout::SetFixedSize);
- /*
- if (!MinecraftVersionList::getMainList().isLoaded())
- {
- TaskDialog *taskDlg = new TaskDialog(this);
- Task *loadTask = MinecraftVersionList::getMainList().getLoadTask();
- loadTask->setParent(taskDlg);
- taskDlg->exec(loadTask);
- }
- */
setSelectedVersion(MMC->minecraftlist()->getLatestStable(), true);
InstIconKey = "infinity";
ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey));
diff --git a/gui/dialogs/NewInstanceDialog.h b/gui/dialogs/NewInstanceDialog.h
index 17045ec0..fa9012ea 100644
--- a/gui/dialogs/NewInstanceDialog.h
+++ b/gui/dialogs/NewInstanceDialog.h
@@ -16,6 +16,7 @@
#pragma once
#include <QDialog>
+
#include "logic/BaseVersion.h"
namespace Ui
diff --git a/gui/groupview/InstanceDelegate.cpp b/gui/groupview/InstanceDelegate.cpp
index 3bd77747..e49e1552 100644
--- a/gui/groupview/InstanceDelegate.cpp
+++ b/gui/groupview/InstanceDelegate.cpp
@@ -113,16 +113,10 @@ void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option
void drawBadges(QPainter *painter, const QStyleOptionViewItemV4 &option, BaseInstance *instance)
{
QList<QString> pixmaps;
- for (auto flag : instance->flags())
+ const BaseInstance::InstanceFlags flags = instance->flags();
+ if (flags & BaseInstance::VersionBrokenFlag)
{
- switch (flag)
- {
- case BaseInstance::VersionBrokenFlag:
- pixmaps.append("broken");
- break;
- default:
- break;
- }
+ pixmaps.append("broken");
}
// begin easter eggs
@@ -160,7 +154,7 @@ void drawBadges(QPainter *painter, const QStyleOptionViewItemV4 &option, BaseIns
{
return;
}
- const QPixmap pixmap = ListViewDelegate::requestPixmap(it.next()).scaled(
+ const QPixmap pixmap = ListViewDelegate::requestBadgePixmap(it.next()).scaled(
itemSide, itemSide, Qt::KeepAspectRatio, Qt::FastTransformation);
painter->drawPixmap(option.rect.width() - x * itemSide + qMax(x - 1, 0) * spacing - itemSide,
y * itemSide + qMax(y - 1, 0) * spacing, itemSide, itemSide,
@@ -354,7 +348,7 @@ QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option,
return sz;
}
-QPixmap ListViewDelegate::requestPixmap(const QString &key)
+QPixmap ListViewDelegate::requestBadgePixmap(const QString &key)
{
if (!m_pixmapCache.contains(key))
{
diff --git a/gui/groupview/InstanceDelegate.h b/gui/groupview/InstanceDelegate.h
index 9ab44864..1520cbb8 100644
--- a/gui/groupview/InstanceDelegate.h
+++ b/gui/groupview/InstanceDelegate.h
@@ -23,7 +23,7 @@ class ListViewDelegate : public QStyledItemDelegate
public:
explicit ListViewDelegate(QObject *parent = 0);
- static QPixmap requestPixmap(const QString &key);
+ static QPixmap requestBadgePixmap(const QString &key);
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option,
diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp
index 9a811577..be6ea184 100644
--- a/logic/BaseInstance.cpp
+++ b/logic/BaseInstance.cpp
@@ -29,6 +29,7 @@
#include <cmdutils.h>
#include "logic/minecraft/MinecraftVersionList.h"
#include "logic/icons/IconList.h"
+#include "logic/InstanceList.h"
BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir,
SettingsObject *settings_obj, QObject *parent)
@@ -143,10 +144,12 @@ QString BaseInstance::minecraftRoot() const
InstanceList *BaseInstance::instList() const
{
- if (parent()->inherits("InstanceList"))
- return (InstanceList *)parent();
- else
- return NULL;
+ return qobject_cast<InstanceList *>(parent());
+}
+
+InstancePtr BaseInstance::getSharedPtr()
+{
+ return instList()->getInstanceById(id());
}
std::shared_ptr<BaseVersionList> BaseInstance::versionList() const
@@ -160,13 +163,12 @@ SettingsObject &BaseInstance::settings() const
return *d->m_settings;
}
-QSet<BaseInstance::InstanceFlag> BaseInstance::flags() const
+BaseInstance::InstanceFlags BaseInstance::flags() const
{
I_D(const BaseInstance);
- return QSet<InstanceFlag>(d->m_flags);
+ return d->m_flags;
}
-
-void BaseInstance::setFlags(const QSet<InstanceFlag> &flags)
+void BaseInstance::setFlags(const InstanceFlags &flags)
{
I_D(BaseInstance);
if (flags != d->m_flags)
@@ -176,10 +178,24 @@ void BaseInstance::setFlags(const QSet<InstanceFlag> &flags)
emit propertiesChanged(this);
}
}
+void BaseInstance::setFlag(const BaseInstance::InstanceFlag flag)
+{
+ I_D(BaseInstance);
+ d->m_flags |= flag;
+ emit flagsChanged();
+ emit propertiesChanged(this);
+}
+void BaseInstance::unsetFlag(const BaseInstance::InstanceFlag flag)
+{
+ I_D(BaseInstance);
+ d->m_flags &= ~flag;
+ emit flagsChanged();
+ emit propertiesChanged(this);
+}
bool BaseInstance::canLaunch() const
{
- return !flags().contains(VersionBrokenFlag);
+ return !(flags() & VersionBrokenFlag);
}
bool BaseInstance::reload()
diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h
index 0cd17de9..ed5840e2 100644
--- a/logic/BaseInstance.h
+++ b/logic/BaseInstance.h
@@ -34,6 +34,10 @@ class OneSixUpdate;
class InstanceList;
class BaseInstancePrivate;
+// pointer for lazy people
+class BaseInstance;
+typedef std::shared_ptr<BaseInstance> InstancePtr;
+
/*!
* \brief Base class for instances.
* This class implements many functions that are common between instances and
@@ -163,6 +167,8 @@ public:
*/
InstanceList *instList() const;
+ InstancePtr getSharedPtr();
+
/*!
* \brief Gets a pointer to this instance's version list.
* \return A pointer to the available version list for this instance.
@@ -193,11 +199,14 @@ public:
enum InstanceFlag
{
- NoFlags = 0x00,
- VersionBrokenFlag = 0x01
+ VersionBrokenFlag = 0x01,
+ UpdateAvailable = 0x02
};
- QSet<InstanceFlag> flags() const;
- void setFlags(const QSet<InstanceFlag> &flags);
+ Q_DECLARE_FLAGS(InstanceFlags, InstanceFlag)
+ InstanceFlags flags() const;
+ void setFlags(const InstanceFlags &flags);
+ void setFlag(const InstanceFlag flag);
+ void unsetFlag(const InstanceFlag flag);
bool canLaunch() const;
@@ -226,7 +235,6 @@ protected:
std::shared_ptr<BaseInstancePrivate> inst_d;
};
-// pointer for lazy people
-typedef std::shared_ptr<BaseInstance> InstancePtr;
-
+Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)
Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
+Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
diff --git a/logic/BaseInstance_p.h b/logic/BaseInstance_p.h
index 77486abc..3d1a4cbe 100644
--- a/logic/BaseInstance_p.h
+++ b/logic/BaseInstance_p.h
@@ -31,6 +31,6 @@ public:
QString m_rootDir;
QString m_group;
std::shared_ptr<SettingsObject> m_settings;
- QSet<BaseInstance::InstanceFlag> m_flags;
+ BaseInstance::InstanceFlags m_flags;
bool m_isRunning = false;
};
diff --git a/logic/BaseVersion.h b/logic/BaseVersion.h
index ed63f551..04d0a10b 100644
--- a/logic/BaseVersion.h
+++ b/logic/BaseVersion.h
@@ -24,6 +24,7 @@
*/
struct BaseVersion
{
+ virtual ~BaseVersion() {}
/*!
* A string used to identify this version in config files.
* This should be unique within the version list or shenanigans will occur.
diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp
index f0f7ffb3..e1cc64df 100644
--- a/logic/InstanceFactory.cpp
+++ b/logic/InstanceFactory.cpp
@@ -52,19 +52,19 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(InstancePtr &inst,
// FIXME: replace with a map lookup, where instance classes register their types
if (inst_type == "OneSix" || inst_type == "Nostalgia")
{
- inst.reset(new OneSixInstance(instDir, m_settings, this));
+ inst.reset(new OneSixInstance(instDir, m_settings));
}
else if (inst_type == "Legacy")
{
- inst.reset(new LegacyInstance(instDir, m_settings, this));
+ inst.reset(new LegacyInstance(instDir, m_settings));
}
else if (inst_type == "LegacyFTB")
{
- inst.reset(new LegacyFTBInstance(instDir, m_settings, this));
+ inst.reset(new LegacyFTBInstance(instDir, m_settings));
}
else if (inst_type == "OneSixFTB")
{
- inst.reset(new OneSixFTBInstance(instDir, m_settings, this));
+ inst.reset(new OneSixFTBInstance(instDir, m_settings));
}
else
{
@@ -82,11 +82,15 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(InstancePtr &in
QLOG_DEBUG() << instDir.toUtf8();
if (!rootDir.exists() && !rootDir.mkpath("."))
{
+ QLOG_ERROR() << "Can't create instance folder" << instDir;
return InstanceFactory::CantCreateDir;
}
auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(version);
if (!mcVer)
+ {
+ QLOG_ERROR() << "Can't create instance for non-existing MC version";
return InstanceFactory::NoSuchVersion;
+ }
auto m_settings = new INISettingsObject(PathCombine(instDir, "instance.cfg"));
m_settings->registerSetting("InstanceType", "Legacy");
@@ -94,7 +98,7 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(InstancePtr &in
if (type == NormalInst)
{
m_settings->set("InstanceType", "OneSix");
- inst.reset(new OneSixInstance(instDir, m_settings, this));
+ inst.reset(new OneSixInstance(instDir, m_settings));
inst->setIntendedVersionId(version->descriptor());
inst->setShouldUseCustomBaseJar(false);
}
@@ -103,14 +107,14 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance(InstancePtr &in
if(mcVer->usesLegacyLauncher())
{
m_settings->set("InstanceType", "LegacyFTB");
- inst.reset(new LegacyFTBInstance(instDir, m_settings, this));
+ inst.reset(new LegacyFTBInstance(instDir, m_settings));
inst->setIntendedVersionId(version->descriptor());
inst->setShouldUseCustomBaseJar(false);
}
else
{
m_settings->set("InstanceType", "OneSixFTB");
- inst.reset(new OneSixFTBInstance(instDir, m_settings, this));
+ inst.reset(new OneSixFTBInstance(instDir, m_settings));
inst->setIntendedVersionId(version->descriptor());
inst->setShouldUseCustomBaseJar(false);
}
diff --git a/logic/InstanceFactory.h b/logic/InstanceFactory.h
index 32a31080..3a4a55a8 100644
--- a/logic/InstanceFactory.h
+++ b/logic/InstanceFactory.h
@@ -26,7 +26,7 @@ struct BaseVersion;
class BaseInstance;
/*!
- * The \bInstanceFactory\b is a singleton that manages loading and creating instances.
+ * The \b InstanceFactory\b is a singleton that manages loading and creating instances.
*/
class InstanceFactory : public QObject
{
diff --git a/logic/LegacyFTBInstance.cpp b/logic/LegacyFTBInstance.cpp
index 73a1f73d..06bef948 100644
--- a/logic/LegacyFTBInstance.cpp
+++ b/logic/LegacyFTBInstance.cpp
@@ -7,7 +7,7 @@ LegacyFTBInstance::LegacyFTBInstance(const QString &rootDir, SettingsObject *set
QString LegacyFTBInstance::getStatusbarDescription()
{
- if (flags().contains(VersionBrokenFlag))
+ if (flags() & VersionBrokenFlag)
{
return "Legacy FTB: " + intendedVersionId() + " (broken)";
}
diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp
index 0239a325..eede7070 100644
--- a/logic/LegacyInstance.cpp
+++ b/logic/LegacyInstance.cpp
@@ -287,7 +287,7 @@ QString LegacyInstance::defaultCustomBaseJar() const
QString LegacyInstance::getStatusbarDescription()
{
- if (flags().contains(VersionBrokenFlag))
+ if (flags() & VersionBrokenFlag)
{
return tr("Legacy : %1 (broken)").arg(intendedVersionId());
}
diff --git a/logic/MMCJson.cpp b/logic/MMCJson.cpp
index 8de88b6b..23af4fff 100644
--- a/logic/MMCJson.cpp
+++ b/logic/MMCJson.cpp
@@ -1,13 +1,26 @@
#include "MMCJson.h"
+
#include <QString>
+#include <QUrl>
#include <QStringList>
#include <math.h>
+QJsonDocument MMCJson::parseDocument(const QByteArray &data, const QString &what)
+{
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &error);
+ if (error.error != QJsonParseError::NoError)
+ {
+ throw JSONValidationError(what + " is not valid JSON: " + error.errorString() + " at " + error.offset);
+ }
+ return doc;
+}
+
bool MMCJson::ensureBoolean(const QJsonValue val, const QString what)
{
if (!val.isBool())
throw JSONValidationError(what + " is not boolean");
- return val.isBool();
+ return val.toBool();
}
QJsonValue MMCJson::ensureExists(QJsonValue val, const QString what)
@@ -24,6 +37,15 @@ QJsonArray MMCJson::ensureArray(const QJsonValue val, const QString what)
return val.toArray();
}
+QJsonArray MMCJson::ensureArray(const QJsonDocument &val, const QString &what)
+{
+ if (!val.isArray())
+ {
+ throw JSONValidationError(what + " is not an array");
+ }
+ return val.array();
+}
+
double MMCJson::ensureDouble(const QJsonValue val, const QString what)
{
if (!val.isDouble())
@@ -60,9 +82,36 @@ QString MMCJson::ensureString(const QJsonValue val, const QString what)
return val.toString();
}
+QUrl MMCJson::ensureUrl(const QJsonValue &val, const QString &what)
+{
+ const QUrl url = QUrl(ensureString(val, what));
+ if (!url.isValid())
+ {
+ throw JSONValidationError(what + " is not an url");
+ }
+ return url;
+}
+
+QJsonDocument MMCJson::parseFile(const QString &filename, const QString &what)
+{
+ QFile f(filename);
+ if (!f.open(QFile::ReadOnly))
+ {
+ throw FileOpenError(f);
+ }
+ return parseDocument(f.readAll(), what);
+}
+
+int MMCJson::ensureInteger(const QJsonValue val, QString what, const int def)
+{
+ if (val.isUndefined())
+ return def;
+ return ensureInteger(val, what);
+}
+
void MMCJson::writeString(QJsonObject &to, QString key, QString value)
{
- if(value.size())
+ if (!value.isEmpty())
{
to.insert(key, value);
}
@@ -70,7 +119,7 @@ void MMCJson::writeString(QJsonObject &to, QString key, QString value)
void MMCJson::writeStringList(QJsonObject &to, QString key, QStringList values)
{
- if(values.size())
+ if (!values.isEmpty())
{
QJsonArray array;
for(auto value: values)
@@ -81,3 +130,13 @@ void MMCJson::writeStringList(QJsonObject &to, QString key, QStringList values)
}
}
+QStringList MMCJson::ensureStringList(const QJsonValue val, QString what)
+{
+ const QJsonArray array = ensureArray(val, what);
+ QStringList out;
+ for (const auto value : array)
+ {
+ out.append(ensureString(value));
+ }
+ return out;
+}
diff --git a/logic/MMCJson.h b/logic/MMCJson.h
index 8408f29b..dc0b4224 100644
--- a/logic/MMCJson.h
+++ b/logic/MMCJson.h
@@ -9,17 +9,29 @@
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
+#include <QFile>
+#include <memory>
#include "MMCError.h"
class JSONValidationError : public MMCError
{
public:
- JSONValidationError(QString cause) : MMCError(cause) {};
- virtual ~JSONValidationError() noexcept {}
+ JSONValidationError(QString cause) : MMCError(cause) {}
+};
+class FileOpenError : public MMCError
+{
+public:
+ FileOpenError(const QFile &file) : MMCError(QObject::tr("Error opening %1: %2").arg(file.fileName(), file.errorString())) {}
};
namespace MMCJson
{
+/// parses the data into a json document. throws if there's a parse error
+QJsonDocument parseDocument(const QByteArray &data, const QString &what);
+
+/// tries to open and then parses the specified file. throws if there's an error
+QJsonDocument parseFile(const QString &filename, const QString &what);
+
/// make sure the value exists. throw otherwise.
QJsonValue ensureExists(QJsonValue val, const QString what = "value");
@@ -27,39 +39,63 @@ QJsonValue ensureExists(QJsonValue val, const QString what = "value");
QJsonObject ensureObject(const QJsonValue val, const QString what = "value");
/// make sure the document is converted into an object. throw otherwise.
-QJsonObject ensureObject(const QJsonDocument val, const QString what = "value");
+QJsonObject ensureObject(const QJsonDocument val, const QString what = "document");
/// make sure the value is converted into an array. throw otherwise.
QJsonArray ensureArray(const QJsonValue val, QString what = "value");
+/// make sure the document is converted into an array. throw otherwise.
+QJsonArray ensureArray(const QJsonDocument &val, const QString &what = "document");
+
/// make sure the value is converted into a string. throw otherwise.
QString ensureString(const QJsonValue val, QString what = "value");
+/// make sure the value is converted into a string that's parseable as an url. throw otherwise.
+QUrl ensureUrl(const QJsonValue &val, const QString &what = "value");
+
/// make sure the value is converted into a boolean. throw otherwise.
bool ensureBoolean(const QJsonValue val, QString what = "value");
/// make sure the value is converted into an integer. throw otherwise.
int ensureInteger(const QJsonValue val, QString what = "value");
+/// make sure the value is converted into an integer. throw otherwise. this version will return the default value if the field is undefined.
+int ensureInteger(const QJsonValue val, QString what, const int def);
+
/// make sure the value is converted into a double precision floating number. throw otherwise.
double ensureDouble(const QJsonValue val, QString what = "value");
+QStringList ensureStringList(const QJsonValue val, QString what);
+
void writeString(QJsonObject & to, QString key, QString value);
-void writeStringList (QJsonObject & to, QString key, QStringList values);
+void writeStringList(QJsonObject & to, QString key, QStringList values);
template <typename T>
-void writeObjectList (QJsonObject & to, QString key, QList<T> values)
+void writeObjectList(QJsonObject & to, QString key, QList<std::shared_ptr<T>> values)
{
- if(values.size())
+ if (!values.isEmpty())
{
QJsonArray array;
- for(auto value: values)
+ for (auto value: values)
{
array.append(value->toJson());
}
to.insert(key, array);
}
}
+template <typename T>
+void writeObjectList(QJsonObject & to, QString key, QList<T> values)
+{
+ if (!values.isEmpty())
+ {
+ QJsonArray array;
+ for (auto value: values)
+ {
+ array.append(value.toJson());
+ }
+ to.insert(key, array);
+ }
+}
}
diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp
index 8dfd3051..903c4437 100644
--- a/logic/OneSixFTBInstance.cpp
+++ b/logic/OneSixFTBInstance.cpp
@@ -118,7 +118,7 @@ bool OneSixFTBInstance::providesVersionFile() const
QString OneSixFTBInstance::getStatusbarDescription()
{
- if (flags().contains(VersionBrokenFlag))
+ if (flags() & VersionBrokenFlag)
{
return "OneSix FTB: " + intendedVersionId() + " (broken)";
}
diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp
index 82d4e480..00d8a9db 100644
--- a/logic/OneSixInstance.cpp
+++ b/logic/OneSixInstance.cpp
@@ -418,7 +418,7 @@ void OneSixInstance::reloadVersion()
try
{
d->version->reload(externalPatches());
- d->m_flags.remove(VersionBrokenFlag);
+ unsetFlag(VersionBrokenFlag);
emit versionReloaded();
}
catch (VersionIncomplete &error)
@@ -427,7 +427,7 @@ void OneSixInstance::reloadVersion()
catch (MMCError &error)
{
d->version->clear();
- d->m_flags.insert(VersionBrokenFlag);
+ setFlag(VersionBrokenFlag);
// TODO: rethrow to show some error message(s)?
emit versionReloaded();
throw;
@@ -464,7 +464,7 @@ QString OneSixInstance::getStatusbarDescription()
{
traits.append(tr("custom"));
}
- if (flags().contains(VersionBrokenFlag))
+ if (flags() & VersionBrokenFlag)
{
traits.append(tr("broken"));
}
@@ -569,3 +569,8 @@ QStringList OneSixInstance::extraArguments() const
}
return list;
}
+
+std::shared_ptr<OneSixInstance> OneSixInstance::getSharedPtr()
+{
+ return std::dynamic_pointer_cast<OneSixInstance>(BaseInstance::getSharedPtr());
+}
diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h
index 75caef1f..f7b01608 100644
--- a/logic/OneSixInstance.h
+++ b/logic/OneSixInstance.h
@@ -96,8 +96,11 @@ public:
virtual bool providesVersionFile() const;
bool reload() override;
+
virtual QStringList extraArguments() const override;
-
+
+ std::shared_ptr<OneSixInstance> getSharedPtr();
+
signals:
void versionReloaded();
@@ -105,3 +108,5 @@ private:
QStringList processMinecraftArgs(AuthSessionPtr account);
QDir reconstructAssets(std::shared_ptr<InstanceVersion> version);
};
+
+Q_DECLARE_METATYPE(std::shared_ptr<OneSixInstance>)
diff --git a/logic/forge/ForgeInstaller.h b/logic/forge/ForgeInstaller.h
index 1c7452d7..655fbc89 100644
--- a/logic/forge/ForgeInstaller.h
+++ b/logic/forge/ForgeInstaller.h
@@ -29,11 +29,11 @@ class ForgeInstaller : public BaseInstaller
friend class ForgeInstallTask;
public:
ForgeInstaller();
- virtual ~ForgeInstaller(){};
+ virtual ~ForgeInstaller(){}
virtual ProgressProvider *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) override;
+ virtual QString id() const override { return "net.minecraftforge"; }
protected:
- virtual QString id() const override { return "net.minecraftforge"; }
void prepare(const QString &filename, const QString &universalUrl);
bool add(OneSixInstance *to) override;
bool addLegacy(OneSixInstance *to);
diff --git a/logic/forge/ForgeVersion.h b/logic/forge/ForgeVersion.h
index c7adc572..e3c1aab9 100644
--- a/logic/forge/ForgeVersion.h
+++ b/logic/forge/ForgeVersion.h
@@ -38,3 +38,5 @@ struct ForgeVersion : public BaseVersion
QString installer_filename;
bool is_recommended = false;
};
+
+Q_DECLARE_METATYPE(ForgeVersionPtr)
diff --git a/logic/java/JavaCheckerJob.h b/logic/java/JavaCheckerJob.h
index 132a92d4..4b79611b 100644
--- a/logic/java/JavaCheckerJob.h
+++ b/logic/java/JavaCheckerJob.h
@@ -59,22 +59,10 @@ public:
{
return javacheckers.size();
}
- virtual void getProgress(qint64 &current, qint64 &total)
- {
- current = current_progress;
- total = total_progress;
- }
- ;
- virtual QString getStatus() const
- {
- return m_job_name;
- }
- ;
virtual bool isRunning() const
{
return m_running;
}
- ;
signals:
void started();
diff --git a/logic/liteloader/LiteLoaderInstaller.h b/logic/liteloader/LiteLoaderInstaller.h
index 43ad6b83..b3336bbd 100644
--- a/logic/liteloader/LiteLoaderInstaller.h
+++ b/logic/liteloader/LiteLoaderInstaller.h
@@ -28,13 +28,10 @@ public:
void prepare(LiteLoaderVersionPtr version);
bool add(OneSixInstance *to) override;
+ virtual QString id() const override { return "com.mumfrey.liteloader"; }
ProgressProvider *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) override;
private:
- virtual QString id() const override
- {
- return "com.mumfrey.liteloader";
- }
LiteLoaderVersionPtr m_version;
};
diff --git a/logic/liteloader/LiteLoaderVersionList.h b/logic/liteloader/LiteLoaderVersionList.h
index 91ed077c..5eecd926 100644
--- a/logic/liteloader/LiteLoaderVersionList.h
+++ b/logic/liteloader/LiteLoaderVersionList.h
@@ -111,3 +111,5 @@ protected:
CacheDownloadPtr listDownload;
LiteLoaderVersionList *m_list;
};
+
+Q_DECLARE_METATYPE(LiteLoaderVersionPtr)
diff --git a/logic/minecraft/InstanceVersion.cpp b/logic/minecraft/InstanceVersion.cpp
index c345e1fb..a243ebf4 100644
--- a/logic/minecraft/InstanceVersion.cpp
+++ b/logic/minecraft/InstanceVersion.cpp
@@ -264,6 +264,14 @@ QList<std::shared_ptr<OneSixLibrary> > InstanceVersion::getActiveNormalLibs()
{
if (lib->isActive() && !lib->isNative())
{
+ for (auto other : output)
+ {
+ if (other->rawName() == lib->rawName())
+ {
+ QLOG_WARN() << "Multiple libraries with name" << lib->rawName() << "in library list!";
+ continue;
+ }
+ }
output.append(lib);
}
}
diff --git a/logic/minecraft/InstanceVersion.h b/logic/minecraft/InstanceVersion.h
index 6b69ab47..664e4242 100644
--- a/logic/minecraft/InstanceVersion.h
+++ b/logic/minecraft/InstanceVersion.h
@@ -140,10 +140,10 @@ public:
QString appletClass;
/// the list of libs - both active and inactive, native and java
- QList<std::shared_ptr<OneSixLibrary>> libraries;
-
+ QList<OneSixLibraryPtr> libraries;
+
/// same, but only vanilla.
- QList<std::shared_ptr<OneSixLibrary>> vanillaLibraries;
+ QList<OneSixLibraryPtr> vanillaLibraries;
/// traits, collected from all the version files (version files can only add)
QSet<QString> traits;
diff --git a/logic/minecraft/MinecraftVersionList.cpp b/logic/minecraft/MinecraftVersionList.cpp
index bde2170b..598fecdb 100644
--- a/logic/minecraft/MinecraftVersionList.cpp
+++ b/logic/minecraft/MinecraftVersionList.cpp
@@ -116,15 +116,10 @@ void MinecraftVersionList::loadBuiltinList()
{
QLOG_INFO() << "Loading builtin version list.";
// grab the version list data from internal resources.
- QResource versionList(":/versions/minecraft.json");
- QFile filez(versionList.absoluteFilePath());
- filez.open(QIODevice::ReadOnly);
- auto data = filez.readAll();
-
- // parse the data as json
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
- QJsonObject root = jsonDoc.object();
+ const QJsonDocument doc =
+ MMCJson::parseFile(":/versions/minecraft.json",
+ "builtin version list");
+ const QJsonObject root = doc.object();
// parse all the versions
for (const auto version : MMCJson::ensureArray(root.value("versions")))
diff --git a/logic/minecraft/OneSixLibrary.h b/logic/minecraft/OneSixLibrary.h
index 2f60405a..1d2ac0cb 100644
--- a/logic/minecraft/OneSixLibrary.h
+++ b/logic/minecraft/OneSixLibrary.h
@@ -48,5 +48,4 @@ public:
/// Constructor
OneSixLibrary(RawLibraryPtr base);
static OneSixLibraryPtr fromRawLibrary(RawLibraryPtr lib);
-
};
diff --git a/logic/minecraft/VersionBuilder.cpp b/logic/minecraft/VersionBuilder.cpp
index dbaf5257..09f761a4 100644
--- a/logic/minecraft/VersionBuilder.cpp
+++ b/logic/minecraft/VersionBuilder.cpp
@@ -288,6 +288,11 @@ static const int currentOrderFileVersion = 1;
bool VersionBuilder::readOverrideOrders(OneSixInstance *instance, PatchOrder &order)
{
QFile orderFile(instance->instanceRoot() + "/order.json");
+ if (!orderFile.exists())
+ {
+ QLOG_WARN() << "Order file doesn't exist. Ignoring.";
+ return false;
+ }
if (!orderFile.open(QFile::ReadOnly))
{
QLOG_ERROR() << "Couldn't open" << orderFile.fileName()
diff --git a/logic/minecraft/VersionFile.cpp b/logic/minecraft/VersionFile.cpp
index 778c5a27..311271fe 100644
--- a/logic/minecraft/VersionFile.cpp
+++ b/logic/minecraft/VersionFile.cpp
@@ -22,8 +22,7 @@ int findLibraryByName(QList<OneSixLibraryPtr> haystack, const GradleSpecifier &n
int retval = -1;
for (int i = 0; i < haystack.size(); ++i)
{
-
- if(haystack.at(i)->rawName().matchName(needle))
+ if (haystack.at(i)->rawName().matchName(needle))
{
// only one is allowed.
if (retval != -1)
@@ -67,7 +66,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi
out->mcVersion = root.value("mcVersion").toString();
out->filename = filename;
- auto readString = [root](const QString & key, QString & variable)
+ auto readString = [root](const QString &key, QString &variable)
{
if (root.contains(key))
{
@@ -75,15 +74,14 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi
}
};
- auto readStringRet = [root](const QString & key)->QString
+ auto readStringRet = [root](const QString &key) -> QString
{
if (root.contains(key))
{
return ensureString(root.value(key));
}
return QString();
- }
- ;
+ };
// FIXME: This should be ignored when applying.
if (!isFTB)
diff --git a/logic/net/ByteArrayDownload.cpp b/logic/net/ByteArrayDownload.cpp
index 27d2a250..a8d1d330 100644
--- a/logic/net/ByteArrayDownload.cpp
+++ b/logic/net/ByteArrayDownload.cpp
@@ -53,16 +53,42 @@ void ByteArrayDownload::downloadError(QNetworkReply::NetworkError error)
QLOG_ERROR() << "Error getting URL:" << m_url.toString().toLocal8Bit()
<< "Network error: " << error;
m_status = Job_Failed;
+ m_errorString = m_reply->errorString();
}
void ByteArrayDownload::downloadFinished()
{
+ if (m_followRedirects)
+ {
+ QVariant redirect = m_reply->header(QNetworkRequest::LocationHeader);
+ QString redirectURL;
+ if(redirect.isValid())
+ {
+ redirectURL = redirect.toString();
+ }
+ // FIXME: This is a hack for https://bugreports.qt-project.org/browse/QTBUG-41061
+ else if(m_reply->hasRawHeader("Location"))
+ {
+ auto data = m_reply->rawHeader("Location");
+ if(data.size() > 2 && data[0] == '/' && data[1] == '/')
+ redirectURL = m_reply->url().scheme() + ":" + data;
+ }
+ if (!redirectURL.isEmpty())
+ {
+ m_url = QUrl(redirect.toString());
+ QLOG_INFO() << "Following redirect to " << m_url.toString();
+ start();
+ return;
+ }
+ }
+
// if the download succeeded
if (m_status != Job_Failed)
{
// nothing went wrong...
m_status = Job_Finished;
m_data = m_reply->readAll();
+ m_content_type = m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
m_reply.reset();
emit succeeded(m_index_within_job);
return;
diff --git a/logic/net/ByteArrayDownload.h b/logic/net/ByteArrayDownload.h
index 76e2e279..85ed2dab 100644
--- a/logic/net/ByteArrayDownload.h
+++ b/logic/net/ByteArrayDownload.h
@@ -31,6 +31,10 @@ public:
/// if not saving to file, downloaded data is placed here
QByteArray m_data;
+ QString m_errorString;
+
+ bool m_followRedirects = false;
+
public
slots:
virtual void start();
diff --git a/logic/net/CacheDownload.cpp b/logic/net/CacheDownload.cpp
index d2a9bdee..6d937e30 100644
--- a/logic/net/CacheDownload.cpp
+++ b/logic/net/CacheDownload.cpp
@@ -101,6 +101,30 @@ void CacheDownload::downloadError(QNetworkReply::NetworkError error)
}
void CacheDownload::downloadFinished()
{
+ if (m_followRedirects)
+ {
+ QVariant redirect = m_reply->header(QNetworkRequest::LocationHeader);
+ QString redirectURL;
+ if(redirect.isValid())
+ {
+ redirectURL = redirect.toString();
+ }
+ // FIXME: This is a hack for https://bugreports.qt-project.org/browse/QTBUG-41061
+ else if(m_reply->hasRawHeader("Location"))
+ {
+ auto data = m_reply->rawHeader("Location");
+ if(data.size() > 2 && data[0] == '/' && data[1] == '/')
+ redirectURL = m_reply->url().scheme() + ":" + data;
+ }
+ if (!redirectURL.isEmpty())
+ {
+ m_url = QUrl(redirect.toString());
+ QLOG_INFO() << "Following redirect to " << m_url.toString();
+ start();
+ return;
+ }
+ }
+
// if the download succeeded
if (m_status == Job_Failed)
{
diff --git a/logic/net/CacheDownload.h b/logic/net/CacheDownload.h
index d446d23e..9eb69fcd 100644
--- a/logic/net/CacheDownload.h
+++ b/logic/net/CacheDownload.h
@@ -36,6 +36,8 @@ private:
bool wroteAnyData = false;
public:
+ bool m_followRedirects = false;
+
explicit CacheDownload(QUrl url, MetaEntryPtr entry);
static CacheDownloadPtr make(QUrl url, MetaEntryPtr entry)
{
diff --git a/logic/net/NetAction.h b/logic/net/NetAction.h
index 97c96e5d..fdd69eec 100644
--- a/logic/net/NetAction.h
+++ b/logic/net/NetAction.h
@@ -55,6 +55,9 @@ public:
/// the network reply
std::shared_ptr<QNetworkReply> m_reply;
+ /// the content of the content-type header
+ QString m_content_type;
+
/// source URL
QUrl m_url;
diff --git a/logic/net/NetJob.h b/logic/net/NetJob.h
index d05e7b6f..8fc04d06 100644
--- a/logic/net/NetJob.h
+++ b/logic/net/NetJob.h
@@ -30,8 +30,8 @@ class NetJob : public ProgressProvider
{
Q_OBJECT
public:
- explicit NetJob(QString job_name) : ProgressProvider(), m_job_name(job_name) {};
- virtual ~NetJob() {};
+ explicit NetJob(QString job_name) : ProgressProvider(), m_job_name(job_name) {}
+ virtual ~NetJob() {}
template <typename T> bool addNetAction(T action)
{
NetActionPtr base = std::static_pointer_cast<NetAction>(action);
@@ -62,7 +62,10 @@ public:
{
return downloads[index];
}
- ;
+ const NetActionPtr at(const int index)
+ {
+ return downloads.at(index);
+ }
NetActionPtr first()
{
if (downloads.size())
@@ -73,21 +76,10 @@ public:
{
return downloads.size();
}
- virtual void getProgress(qint64 &current, qint64 &total)
- {
- current = current_progress;
- total = total_progress;
- }
- ;
- virtual QString getStatus() const
- {
- return m_job_name;
- }
virtual bool isRunning() const
{
return m_running;
}
- ;
QStringList getFailedFiles();
signals:
void started();
diff --git a/logic/tasks/ProgressProvider.h b/logic/tasks/ProgressProvider.h
index dcb71139..f6928e1c 100644
--- a/logic/tasks/ProgressProvider.h
+++ b/logic/tasks/ProgressProvider.h
@@ -32,9 +32,7 @@ signals:
void status(QString status);
public:
- virtual ~ProgressProvider() {};
- virtual QString getStatus() const = 0;
- virtual void getProgress(qint64 &current, qint64 &total) = 0;
+ virtual ~ProgressProvider() {}
virtual bool isRunning() const = 0;
public
slots:
diff --git a/logic/tasks/SequentialTask.cpp b/logic/tasks/SequentialTask.cpp
index e0f8fcdd..e08206f8 100644
--- a/logic/tasks/SequentialTask.cpp
+++ b/logic/tasks/SequentialTask.cpp
@@ -1,31 +1,7 @@
#include "SequentialTask.h"
-SequentialTask::SequentialTask(QObject *parent) :
- Task(parent), m_currentIndex(-1)
+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<ProgressProvider> task)
@@ -55,10 +31,9 @@ void SequentialTask::startNext()
std::shared_ptr<ProgressProvider> 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(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64)));
connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext()));
next->start();
- emit status(getStatus());
}
void SequentialTask::subTaskFailed(const QString &msg)
@@ -69,16 +44,16 @@ void SequentialTask::subTaskStatus(const QString &msg)
{
setStatus(msg);
}
-void SequentialTask::subTaskProgress()
+void SequentialTask::subTaskProgress(qint64 current, qint64 total)
{
- qint64 current, total;
- getProgress(current, total);
- if (total == 0)
+ if(total == 0)
{
setProgress(0);
+ return;
}
- else
- {
- setProgress(100 * current / total);
- }
+ auto dcurrent = (double) current;
+ auto dtotal = (double) total;
+ auto partial = ((dcurrent / dtotal) * 100.0f)/* / double(m_queue.size())*/;
+ // auto bigpartial = double(m_currentIndex) * 100.0f / double(m_queue.size());
+ setProgress(partial);
}
diff --git a/logic/tasks/SequentialTask.h b/logic/tasks/SequentialTask.h
index c405dca3..f1fe9c72 100644
--- a/logic/tasks/SequentialTask.h
+++ b/logic/tasks/SequentialTask.h
@@ -11,9 +11,6 @@ class SequentialTask : public Task
public:
explicit SequentialTask(QObject *parent = 0);
- virtual QString getStatus() const;
- virtual void getProgress(qint64 &current, qint64 &total);
-
void addTask(std::shared_ptr<ProgressProvider> task);
protected:
@@ -24,7 +21,7 @@ slots:
void startNext();
void subTaskFailed(const QString &msg);
void subTaskStatus(const QString &msg);
- void subTaskProgress();
+ void subTaskProgress(qint64 current, qint64 total);
private:
QQueue<std::shared_ptr<ProgressProvider> > m_queue;
diff --git a/logic/tasks/Task.cpp b/logic/tasks/Task.cpp
index c980d6b3..f7a99cdf 100644
--- a/logic/tasks/Task.cpp
+++ b/logic/tasks/Task.cpp
@@ -20,29 +20,16 @@ Task::Task(QObject *parent) : ProgressProvider(parent)
{
}
-QString Task::getStatus() const
-{
- return m_statusString;
-}
-
void Task::setStatus(const QString &new_status)
{
- m_statusString = new_status;
emit status(new_status);
}
void Task::setProgress(int new_progress)
{
- m_progress = new_progress;
emit progress(new_progress, 100);
}
-void Task::getProgress(qint64 &current, qint64 &total)
-{
- current = m_progress;
- total = 100;
-}
-
void Task::start()
{
m_running = true;
@@ -61,6 +48,7 @@ void Task::emitFailed(QString reason)
void Task::emitSucceeded()
{
+ if (!m_running) { return; } // Don't succeed twice.
m_running = false;
m_succeeded = true;
QLOG_INFO() << "Task succeeded";
diff --git a/logic/tasks/Task.h b/logic/tasks/Task.h
index 063eeeda..e6b09262 100644
--- a/logic/tasks/Task.h
+++ b/logic/tasks/Task.h
@@ -26,8 +26,6 @@ public:
explicit Task(QObject *parent = 0);
virtual ~Task() {};
- virtual QString getStatus() const;
- virtual void getProgress(qint64 &current, qint64 &total);
virtual bool isRunning() const;
/*!
@@ -50,6 +48,7 @@ slots:
protected:
virtual void executeTask() = 0;
+protected slots:
virtual void emitSucceeded();
virtual void emitFailed(QString reason);
@@ -59,9 +58,8 @@ slots:
void setProgress(int progress);
protected:
- QString m_statusString;
- int m_progress = 0;
bool m_running = false;
bool m_succeeded = false;
QString m_failReason = "";
};
+
diff --git a/logic/tools/BaseExternalTool.h b/logic/tools/BaseExternalTool.h
index 7672bf10..5f112970 100644
--- a/logic/tools/BaseExternalTool.h
+++ b/logic/tools/BaseExternalTool.h
@@ -43,7 +43,7 @@ public:
virtual QString name() const = 0;
- virtual void registerSettings(SettingsObject *settings) = 0;
+ virtual void registerSettings(std::shared_ptr<SettingsObject> settings) = 0;
virtual BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) = 0;
diff --git a/logic/tools/JProfiler.cpp b/logic/tools/JProfiler.cpp
index 988a875a..2134984e 100644
--- a/logic/tools/JProfiler.cpp
+++ b/logic/tools/JProfiler.cpp
@@ -40,7 +40,7 @@ void JProfiler::beginProfilingImpl(MinecraftProcess *process)
m_profilerProcess = profiler;
}
-void JProfilerFactory::registerSettings(SettingsObject *settings)
+void JProfilerFactory::registerSettings(std::shared_ptr<SettingsObject> settings)
{
settings->registerSetting("JProfilerPath");
settings->registerSetting("JProfilerPort", 42042);
diff --git a/logic/tools/JProfiler.h b/logic/tools/JProfiler.h
index b3fa6ec7..3085511b 100644
--- a/logic/tools/JProfiler.h
+++ b/logic/tools/JProfiler.h
@@ -16,7 +16,7 @@ class JProfilerFactory : public BaseProfilerFactory
{
public:
QString name() const override { return "JProfiler"; }
- void registerSettings(SettingsObject *settings) override;
+ void registerSettings(std::shared_ptr<SettingsObject> settings) override;
BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override;
bool check(QString *error) override;
bool check(const QString &path, QString *error) override;
diff --git a/logic/tools/JVisualVM.cpp b/logic/tools/JVisualVM.cpp
index 09ab1762..33e271b4 100644
--- a/logic/tools/JVisualVM.cpp
+++ b/logic/tools/JVisualVM.cpp
@@ -37,7 +37,7 @@ void JVisualVM::beginProfilingImpl(MinecraftProcess *process)
m_profilerProcess = profiler;
}
-void JVisualVMFactory::registerSettings(SettingsObject *settings)
+void JVisualVMFactory::registerSettings(std::shared_ptr<SettingsObject> settings)
{
QString defaultValue = QStandardPaths::findExecutable("jvisualvm");
if (defaultValue.isNull())
diff --git a/logic/tools/JVisualVM.h b/logic/tools/JVisualVM.h
index dffdde36..a646c681 100644
--- a/logic/tools/JVisualVM.h
+++ b/logic/tools/JVisualVM.h
@@ -16,7 +16,7 @@ class JVisualVMFactory : public BaseProfilerFactory
{
public:
QString name() const override { return "JVisualVM"; }
- void registerSettings(SettingsObject *settings) override;
+ void registerSettings(std::shared_ptr<SettingsObject> settings) override;
BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override;
bool check(QString *error) override;
bool check(const QString &path, QString *error) override;
diff --git a/logic/tools/MCEditTool.cpp b/logic/tools/MCEditTool.cpp
index 91643585..1c7d9cd2 100644
--- a/logic/tools/MCEditTool.cpp
+++ b/logic/tools/MCEditTool.cpp
@@ -43,7 +43,7 @@ void MCEditTool::runImpl()
#endif
}
-void MCEditFactory::registerSettings(SettingsObject *settings)
+void MCEditFactory::registerSettings(std::shared_ptr<SettingsObject> settings)
{
settings->registerSetting("MCEditPath");
}
diff --git a/logic/tools/MCEditTool.h b/logic/tools/MCEditTool.h
index 9985bde1..810caf25 100644
--- a/logic/tools/MCEditTool.h
+++ b/logic/tools/MCEditTool.h
@@ -16,7 +16,7 @@ class MCEditFactory : public BaseDetachedToolFactory
{
public:
QString name() const override { return "MCEdit"; }
- void registerSettings(SettingsObject *settings) override;
+ void registerSettings(std::shared_ptr<SettingsObject> settings) override;
BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override;
bool check(QString *error) override;
bool check(const QString &path, QString *error) override;
diff --git a/resources/instances/instances.qrc b/resources/instances/instances.qrc
index 5124b846..09ae25d0 100644
--- a/resources/instances/instances.qrc
+++ b/resources/instances/instances.qrc
@@ -38,6 +38,9 @@
<file>herobrine.png</file>
<file>derp.png</file>
+ <!-- Update. GPLv2, https://code.google.com/p/gnome-colors/ -->
+ <file>updateavailable.png</file>
+
<!-- Source: http://www.iconarchive.com/show/cat-icons-by-fasticon/Cat-Brown-icon.html -->
<file>kitten.png</file>
<!-- Source: http://www.iconarchive.com/show/crystal-clear-icons-by-everaldo/Filesystem-file-broken-icon.html -->
diff --git a/resources/instances/updateavailable.png b/resources/instances/updateavailable.png
new file mode 100644
index 00000000..754005f9
--- /dev/null
+++ b/resources/instances/updateavailable.png
Binary files differ
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index d56988e0..7afb3f80 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -24,6 +24,7 @@ endmacro()
add_unit_test(pathutils tst_pathutils.cpp)
add_unit_test(gradlespecifier tst_gradlespecifier.cpp)
add_unit_test(userutils tst_userutils.cpp)
+add_unit_test(modutils tst_modutils.cpp)
add_unit_test(inifile tst_inifile.cpp)
add_unit_test(UpdateChecker tst_UpdateChecker.cpp)
add_unit_test(DownloadUpdateTask tst_DownloadUpdateTask.cpp)
diff --git a/tests/TestUtil.h b/tests/TestUtil.h
index e7017743..87a910d9 100644
--- a/tests/TestUtil.h
+++ b/tests/TestUtil.h
@@ -9,29 +9,30 @@
#include "test_config.h"
-struct TestsInternal
+class TestsInternal
{
- static QByteArray readFile(const QString &fileName)
- {
- QFile f(fileName);
- f.open(QFile::ReadOnly);
- return f.readAll();
- }
- static QString readFileUtf8(const QString &fileName)
- {
- return QString::fromUtf8(readFile(fileName));
- }
+public:
+ static QByteArray readFile(const QString &fileName)
+ {
+ QFile f(fileName);
+ f.open(QFile::ReadOnly);
+ return f.readAll();
+ }
+ static QString readFileUtf8(const QString &fileName)
+ {
+ return QString::fromUtf8(readFile(fileName));
+ }
};
-#define MULTIMC_GET_TEST_FILE(file) TestsInternal::readFile(QFINDTESTDATA( file ))
-#define MULTIMC_GET_TEST_FILE_UTF8(file) TestsInternal::readFileUtf8(QFINDTESTDATA( file ))
+#define MULTIMC_GET_TEST_FILE(file) TestsInternal::readFile(QFINDTESTDATA(file))
+#define MULTIMC_GET_TEST_FILE_UTF8(file) TestsInternal::readFileUtf8(QFINDTESTDATA(file))
#ifdef Q_OS_LINUX
-# define _MMC_EXTRA_ARGV , "-platform", "offscreen"
-# define _MMC_EXTRA_ARGC 2
+#define _MMC_EXTRA_ARGV , "-platform", "offscreen"
+#define _MMC_EXTRA_ARGC 2
#else
-# define _MMC_EXTRA_ARGV
-# define _MMC_EXTRA_ARGC 0
+#define _MMC_EXTRA_ARGV
+#define _MMC_EXTRA_ARGC 0
#endif
diff --git a/tests/tst_modutils.cpp b/tests/tst_modutils.cpp
new file mode 100644
index 00000000..e49548bc
--- /dev/null
+++ b/tests/tst_modutils.cpp
@@ -0,0 +1,153 @@
+/* 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 <QTest>
+
+#include "modutils.h"
+#include "TestUtil.h"
+
+class ModUtilsTest : public QObject
+{
+ Q_OBJECT
+ void setupVersions()
+ {
+ QTest::addColumn<QString>("first");
+ QTest::addColumn<QString>("second");
+ QTest::addColumn<bool>("lessThan");
+ QTest::addColumn<bool>("equal");
+
+ QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true;
+ QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true;
+ QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true;
+ QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true;
+
+ QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false;
+ QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false;
+ QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false;
+ QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false;
+ QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false;
+ QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false;
+ QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false;
+
+ QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false;
+ QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false;
+ QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false;
+ QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false;
+ QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false;
+ QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false;
+ QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false;
+ }
+
+private slots:
+ void initTestCase()
+ {
+
+ }
+ void cleanupTestCase()
+ {
+
+ }
+
+ void test_versionIsInInterval_data()
+ {
+ QTest::addColumn<QString>("version");
+ QTest::addColumn<QString>("interval");
+ QTest::addColumn<bool>("result");
+
+ QTest::newRow("empty, true") << "1.2.3" << "" << true;
+ QTest::newRow("one version, true") << "1.2.3" << "1.2.3" << true;
+ QTest::newRow("one version, false") << "1.2.3" << "1.2.2" << false;
+
+ QTest::newRow("one version inclusive <-> infinity, true") << "1.2.3" << "[1.2.3,)" << true;
+ QTest::newRow("one version exclusive <-> infinity, true") << "1.2.3" << "(1.2.2,)" << true;
+ QTest::newRow("one version inclusive <-> infitity, false") << "1.2.3" << "[1.2.4,)" << false;
+ QTest::newRow("one version exclusive <-> infinity, false") << "1.2.3" << "(1.2.3,)" << false;
+
+ QTest::newRow("infinity <-> one version inclusive, true") << "1.2.3" << "(,1.2.3]" << true;
+ QTest::newRow("infinity <-> one version exclusive, true") << "1.2.3" << "(,1.2.4)" << true;
+ QTest::newRow("infinity <-> one version inclusive, false") << "1.2.3" << "(,1.2.2]" << false;
+ QTest::newRow("infinity <-> one version exclusive, false") << "1.2.3" << "(,1.2.3)" << false;
+
+ QTest::newRow("inclusive <-> inclusive, true") << "1.2.3" << "[1.2.2,1.2.3]" << true;
+ QTest::newRow("inclusive <-> exclusive, true") << "1.2.3" << "[1.2.3,1.2.4)" << true;
+ QTest::newRow("exclusive <-> inclusive, true") << "1.2.3" << "(1.2.2,1.2.3]" << true;
+ QTest::newRow("exclusive <-> exclusive, true") << "1.2.3" << "(1.2.2,1.2.4)" << true;
+ QTest::newRow("inclusive <-> inclusive, false") << "1.2.3" << "[1.0.0,1.2.2]" << false;
+ QTest::newRow("inclusive <-> exclusive, false") << "1.2.3" << "[1.0.0,1.2.3)" << false;
+ QTest::newRow("exclusive <-> inclusive, false") << "1.2.3" << "(1.2.3,2.0.0]" << false;
+ QTest::newRow("exclusive <-> exclusive, false") << "1.2.3" << "(1.0.0,1.2.3)" << false;
+ }
+ void test_versionIsInInterval()
+ {
+ QFETCH(QString, version);
+ QFETCH(QString, interval);
+ QFETCH(bool, result);
+
+ QCOMPARE(Util::versionIsInInterval(version, interval), result);
+ }
+
+ void test_versionCompareLessThan_data()
+ {
+ setupVersions();
+ }
+ void test_versionCompareLessThan()
+ {
+ QFETCH(QString, first);
+ QFETCH(QString, second);
+ QFETCH(bool, lessThan);
+ QFETCH(bool, equal);
+
+ const auto v1 = Util::Version(first);
+ const auto v2 = Util::Version(second);
+
+ QCOMPARE(v1 < v2, lessThan);
+ }
+ void test_versionCompareGreaterThan_data()
+ {
+ setupVersions();
+ }
+ void test_versionCompareGreaterThan()
+ {
+ QFETCH(QString, first);
+ QFETCH(QString, second);
+ QFETCH(bool, lessThan);
+ QFETCH(bool, equal);
+
+ const auto v1 = Util::Version(first);
+ const auto v2 = Util::Version(second);
+
+ QCOMPARE(v1 > v2, !lessThan && !equal);
+ }
+ void test_versionCompareEqual_data()
+ {
+ setupVersions();
+ }
+ void test_versionCompareEqual()
+ {
+ QFETCH(QString, first);
+ QFETCH(QString, second);
+ QFETCH(bool, lessThan);
+ QFETCH(bool, equal);
+
+ const auto v1 = Util::Version(first);
+ const auto v2 = Util::Version(second);
+
+ QCOMPARE(v1 == v2, equal);
+ }
+};
+
+QTEST_GUILESS_MAIN(ModUtilsTest)
+
+#include "tst_modutils.moc"