diff options
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 ¤t, 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 ¤t, 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 ¤t, 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 ¤t, 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 ¤t, 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 ¤t, 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 ¤t, 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 Binary files differnew file mode 100644 index 00000000..754005f9 --- /dev/null +++ b/resources/instances/updateavailable.png 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" |