From 1a0bbdd9acaf1c5edaad6d3f0790c1b02674c0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 21 Feb 2016 01:44:27 +0100 Subject: GH-1453 report version file problems in the version page --- application/pages/VersionPage.cpp | 96 +++++++++++++++++++++++++++++++++++- application/pages/VersionPage.h | 2 + application/pages/VersionPage.ui | 24 +++++++-- application/widgets/ModListView.cpp | 4 ++ logic/minecraft/MinecraftProfile.cpp | 26 +++++++++- logic/minecraft/MinecraftVersion.cpp | 54 +++++++++++++++++--- logic/minecraft/MinecraftVersion.h | 7 +++ logic/minecraft/OneSixInstance.cpp | 19 +++++++ logic/minecraft/ProfilePatch.h | 54 ++++++++++++++++++++ logic/minecraft/ProfileUtils.cpp | 19 +++++-- logic/minecraft/VersionFile.cpp | 26 +++++----- 11 files changed, 305 insertions(+), 26 deletions(-) diff --git a/application/pages/VersionPage.cpp b/application/pages/VersionPage.cpp index e1abcf0c..f01a2d62 100644 --- a/application/pages/VersionPage.cpp +++ b/application/pages/VersionPage.cpp @@ -47,6 +47,51 @@ #include "icons/IconList.h" #include "Exception.h" +#include "MultiMC.h" + +class IconProxy : public QIdentityProxyModel +{ + Q_OBJECT +public: + + IconProxy(QWidget *parentWidget) : QIdentityProxyModel(parentWidget) + { + connect(parentWidget, &QObject::destroyed, this, &IconProxy::widgetGone); + m_parentWidget = parentWidget; + } + + virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override + { + QVariant var = QIdentityProxyModel::data(mapToSource(proxyIndex), role); + int column = proxyIndex.column(); + if(column == 0 && role == Qt::DecorationRole && m_parentWidget) + { + if(!var.isNull()) + { + auto string = var.toString(); + if(string == "warning") + { + return MMC->getThemedIcon("status-yellow"); + } + else if(string == "error") + { + return MMC->getThemedIcon("status-bad"); + } + } + return MMC->getThemedIcon("status-good"); + } + return var; + } +private slots: + void widgetGone() + { + m_parentWidget = nullptr; + } + +private: + QWidget *m_parentWidget = nullptr; +}; + QIcon VersionPage::icon() const { return ENV.icons()->getIcon(m_inst->iconKey()); @@ -72,11 +117,15 @@ VersionPage::VersionPage(OneSixInstance *inst, QWidget *parent) m_version = m_inst->getMinecraftProfile(); if (m_version) { - ui->packageView->setModel(m_version.get()); + auto proxy = new IconProxy(ui->packageView); + proxy->setSourceModel(m_version.get()); + ui->packageView->setModel(proxy); ui->packageView->installEventFilter(this); ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection); connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent); + auto smodel = ui->packageView->selectionModel(); + connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(packageCurrent(QModelIndex, QModelIndex))); updateVersionControls(); // select first item. preselect(0); @@ -94,6 +143,49 @@ VersionPage::~VersionPage() delete ui; } +void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex &previous) +{ + if (!current.isValid()) + { + ui->frame->clear(); + return; + } + int row = current.row(); + auto patch = m_version->versionPatch(row); + auto severity = patch->getProblemSeverity(); + switch(severity) + { + case PROBLEM_WARNING: + ui->frame->setModText(tr("%1 possibly has issues.").arg(patch->getPatchName())); + break; + case PROBLEM_ERROR: + ui->frame->setModText(tr("%1 has issues!").arg(patch->getPatchName())); + break; + default: + case PROBLEM_NONE: + ui->frame->clear(); + return; + } + + auto &problems = patch->getProblems(); + QString problemOut; + for (auto &problem: problems) + { + if(problem.getSeverity() == PROBLEM_ERROR) + { + problemOut += tr("Error: "); + } + else if(problem.getSeverity() == PROBLEM_WARNING) + { + problemOut += tr("Warning: "); + } + problemOut += problem.getDescription(); + problemOut += "\n"; + } + ui->frame->setModDescription(problemOut); +} + + void VersionPage::updateVersionControls() { ui->forgeBtn->setEnabled(true); @@ -453,3 +545,5 @@ void VersionPage::on_revertBtn_clicked() updateButtons(); preselect(currentIdx); } + +#include "VersionPage.moc" diff --git a/application/pages/VersionPage.h b/application/pages/VersionPage.h index 8f2bf024..216bc30e 100644 --- a/application/pages/VersionPage.h +++ b/application/pages/VersionPage.h @@ -90,4 +90,6 @@ public slots: private slots: void onGameUpdateError(QString error); + void packageCurrent(const QModelIndex ¤t, const QModelIndex &previous); + }; diff --git a/application/pages/VersionPage.ui b/application/pages/VersionPage.ui index e88888c8..c16208db 100644 --- a/application/pages/VersionPage.ui +++ b/application/pages/VersionPage.ui @@ -7,7 +7,7 @@ 0 0 693 - 575 + 750 @@ -32,8 +32,8 @@ Tab 1 - - + + Qt::ScrollBarAlwaysOn @@ -49,7 +49,7 @@ - + @@ -245,6 +245,16 @@ + + + + + 0 + 0 + + + + @@ -263,6 +273,12 @@
widgets/LineSeparator.h
1 + + MCModInfoFrame + QFrame +
widgets/MCModInfoFrame.h
+ 1 +
tabWidget diff --git a/application/widgets/ModListView.cpp b/application/widgets/ModListView.cpp index aa16ad05..51b4a475 100644 --- a/application/widgets/ModListView.cpp +++ b/application/widgets/ModListView.cpp @@ -46,6 +46,10 @@ void ModListView::setModel ( QAbstractItemModel* model ) head->setStretchLastSection(false); // HACK: this is true for the checkbox column of mod lists auto string = model->headerData(0,head->orientation()).toString(); + if(head->count() < 1) + { + return; + } if(!string.size()) { head->setSectionResizeMode(0, QHeaderView::ResizeToContents); diff --git a/logic/minecraft/MinecraftProfile.cpp b/logic/minecraft/MinecraftProfile.cpp index 4f33920b..360fa575 100644 --- a/logic/minecraft/MinecraftProfile.cpp +++ b/logic/minecraft/MinecraftProfile.cpp @@ -295,6 +295,8 @@ QVariant MinecraftProfile::data(const QModelIndex &index, int role) const if (row < 0 || row >= VersionPatches.size()) return QVariant(); + auto patch = VersionPatches.at(row); + if (role == Qt::DisplayRole) { switch (column) @@ -303,7 +305,6 @@ QVariant MinecraftProfile::data(const QModelIndex &index, int role) const return VersionPatches.at(row)->getPatchName(); case 1: { - auto patch = VersionPatches.at(row); if(patch->isCustom()) { return QString("%1 (Custom)").arg(patch->getPatchVersion()); @@ -317,6 +318,29 @@ QVariant MinecraftProfile::data(const QModelIndex &index, int role) const return QVariant(); } } + if(role == Qt::DecorationRole) + { + switch(column) + { + case 0: + { + auto severity = patch->getProblemSeverity(); + switch (severity) + { + case PROBLEM_WARNING: + return "warning"; + case PROBLEM_ERROR: + return "error"; + default: + return QVariant(); + } + } + default: + { + return QVariant(); + } + } + } return QVariant(); } QVariant MinecraftProfile::headerData(int section, Qt::Orientation orientation, int role) const diff --git a/logic/minecraft/MinecraftVersion.cpp b/logic/minecraft/MinecraftVersion.cpp index 96d64d6d..e6895bf7 100644 --- a/logic/minecraft/MinecraftVersion.cpp +++ b/logic/minecraft/MinecraftVersion.cpp @@ -89,12 +89,36 @@ QString MinecraftVersion::getUrl() const VersionFilePtr MinecraftVersion::getVersionFile() { QFileInfo versionFile(QString("versions/%1/%1.dat").arg(m_descriptor)); - - auto loadedVersionFile = ProfileUtils::parseBinaryJsonFile(versionFile); - loadedVersionFile->name = "Minecraft"; - //FIXME: possibly not the best place for this... but w/e - loadedVersionFile->setCustomizable(true); - return loadedVersionFile; + m_problems.clear(); + m_problemSeverity = PROBLEM_NONE; + if(!versionFile.exists()) + { + if(m_loadedVersionFile) + { + m_loadedVersionFile.reset(); + } + addProblem(PROBLEM_WARNING, QObject::tr("The patch file doesn't exist locally. It's possible it just needs to be downloaded.")); + } + else + { + try + { + if(versionFile.lastModified() != m_loadedVersionFileTimestamp) + { + auto loadedVersionFile = ProfileUtils::parseBinaryJsonFile(versionFile); + loadedVersionFile->name = "Minecraft"; + loadedVersionFile->setCustomizable(true); + m_loadedVersionFileTimestamp = versionFile.lastModified(); + m_loadedVersionFile = loadedVersionFile; + } + } + catch(Exception e) + { + m_loadedVersionFile.reset(); + addProblem(PROBLEM_ERROR, QObject::tr("The patch file couldn't be read:\n%1").arg(e.cause())); + } + } + return m_loadedVersionFile; } bool MinecraftVersion::isCustomizable() @@ -114,6 +138,24 @@ bool MinecraftVersion::isCustomizable() return false; } +const QList &MinecraftVersion::getProblems() +{ + if(m_versionSource != Builtin && getVersionFile()) + { + return getVersionFile()->getProblems(); + } + return ProfilePatch::getProblems(); +} + +ProblemSeverity MinecraftVersion::getProblemSeverity() +{ + if(m_versionSource != Builtin && getVersionFile()) + { + return getVersionFile()->getProblemSeverity(); + } + return ProfilePatch::getProblemSeverity(); +} + void MinecraftVersion::applyTo(MinecraftProfile *version) { // do we have this one cached? diff --git a/logic/minecraft/MinecraftVersion.h b/logic/minecraft/MinecraftVersion.h index 120ea8ce..afeac3b7 100644 --- a/logic/minecraft/MinecraftVersion.h +++ b/logic/minecraft/MinecraftVersion.h @@ -78,6 +78,9 @@ public: /* methods */ QString getUrl() const; + virtual const QList &getProblems() override; + virtual ProblemSeverity getProblemSeverity() override; + private: /* methods */ void applyFileTo(MinecraftProfile *version); @@ -124,4 +127,8 @@ public: /* data */ /// an update available from Mojang MinecraftVersionPtr upstreamUpdate; + +private: /* data */ + QDateTime m_loadedVersionFileTimestamp; + mutable VersionFilePtr m_loadedVersionFile; }; diff --git a/logic/minecraft/OneSixInstance.cpp b/logic/minecraft/OneSixInstance.cpp index da1b21cb..9a3920cc 100644 --- a/logic/minecraft/OneSixInstance.cpp +++ b/logic/minecraft/OneSixInstance.cpp @@ -111,6 +111,14 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) // blatant self-promotion. token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; + if(m_version->isVanilla()) + { + token_mapping["version_type"] = m_version->type; + } + else + { + token_mapping["version_type"] = "custom"; + } QString absRootDir = QDir(minecraftRoot()).absolutePath(); token_mapping["game_directory"] = absRootDir; @@ -119,10 +127,21 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) token_mapping["user_properties"] = session->serializeUserProperties(); token_mapping["user_type"] = session->user_type; + // 1.7.3+ assets tokens token_mapping["assets_root"] = absAssetsDir; token_mapping["assets_index_name"] = m_version->assets; + // 1.9+ version type token + if(m_version->isVanilla()) + { + token_mapping["version_type"] = m_version->type; + } + else + { + token_mapping["version_type"] = "custom"; + } + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); for (int i = 0; i < parts.length(); i++) { diff --git a/logic/minecraft/ProfilePatch.h b/logic/minecraft/ProfilePatch.h index de42cb7a..e4bb1c02 100644 --- a/logic/minecraft/ProfilePatch.h +++ b/logic/minecraft/ProfilePatch.h @@ -6,6 +6,35 @@ #include "JarMod.h" class MinecraftProfile; + +enum ProblemSeverity +{ + PROBLEM_NONE, + PROBLEM_WARNING, + PROBLEM_ERROR +}; + +class PatchProblem +{ +public: + PatchProblem(ProblemSeverity severity, const QString & description) + { + m_severity = severity; + m_description = description; + } + const QString & getDescription() const + { + return m_description; + } + const ProblemSeverity getSeverity() const + { + return m_severity; + } +private: + ProblemSeverity m_severity; + QString m_description; +}; + class ProfilePatch { public: @@ -32,6 +61,31 @@ public: virtual QString getPatchName() = 0; virtual QString getPatchVersion() = 0; virtual QString getPatchFilename() = 0; + + virtual const QList& getProblems() + { + return m_problems; + } + virtual void addProblem(ProblemSeverity severity, const QString &description) + { + if(severity > m_problemSeverity) + { + m_problemSeverity = severity; + } + m_problems.append(PatchProblem(severity, description)); + } + virtual ProblemSeverity getProblemSeverity() + { + return m_problemSeverity; + } + virtual bool hasFailed() + { + return getProblemSeverity() == PROBLEM_ERROR; + } + +protected: + QList m_problems; + ProblemSeverity m_problemSeverity = PROBLEM_NONE; }; typedef std::shared_ptr ProfilePatchPtr; diff --git a/logic/minecraft/ProfileUtils.cpp b/logic/minecraft/ProfileUtils.cpp index 9a886f1d..5816f207 100644 --- a/logic/minecraft/ProfileUtils.cpp +++ b/logic/minecraft/ProfileUtils.cpp @@ -107,13 +107,26 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder) .arg(fileInfo.fileName(), file.errorString())); } QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + auto data = file.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data, &error); if (error.error != QJsonParseError::NoError) { + int line = 0; + int column = 0; + for(int i = 0; i < error.offset; i++) + { + if(data[i] == '\n') + { + line++; + column = 0; + continue; + } + column++; + } throw JSONValidationError( - QObject::tr("Unable to process the version file %1: %2 at %3.") + QObject::tr("Unable to process the version file %1: %2 at line %3 column %4.") .arg(fileInfo.fileName(), error.errorString()) - .arg(error.offset)); + .arg(line).arg(column)); } return VersionFile::fromJson(doc, file.fileName(), requireOrder); } diff --git a/logic/minecraft/VersionFile.cpp b/logic/minecraft/VersionFile.cpp index 50f4c289..fee83900 100644 --- a/logic/minecraft/VersionFile.cpp +++ b/logic/minecraft/VersionFile.cpp @@ -15,8 +15,6 @@ using namespace Json; #include "VersionBuildError.h" #include -#define CURRENT_MINIMUM_LAUNCHER_VERSION 18 - static void readString(const QJsonObject &root, const QString &key, QString &variable) { if (root.contains(key)) @@ -50,6 +48,19 @@ int findLibraryByName(QList haystack, const GradleSpecifier &n return retval; } +void checkMinimumLauncherVersion(VersionFilePtr out) +{ + const int CURRENT_MINIMUM_LAUNCHER_VERSION = 14; + if (out->minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) + { + out->addProblem( + PROBLEM_WARNING, + QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by MultiMC (%2). It might not work properly!") + .arg(out->minimumLauncherVersion) + .arg(CURRENT_MINIMUM_LAUNCHER_VERSION)); + } +} + VersionFilePtr VersionFile::fromMojangJson(const QJsonDocument &doc, const QString &filename) { VersionFilePtr out(new VersionFile()); @@ -82,6 +93,7 @@ VersionFilePtr VersionFile::fromMojangJson(const QJsonDocument &doc, const QStri if (root.contains("minimumLauncherVersion")) { out->minimumLauncherVersion = requireInteger(root.value("minimumLauncherVersion")); + checkMinimumLauncherVersion(out); } if (root.contains("libraries")) @@ -149,6 +161,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi if (root.contains("minimumLauncherVersion")) { out->minimumLauncherVersion = requireInteger(root.value("minimumLauncherVersion")); + checkMinimumLauncherVersion(out); } if (root.contains("tweakers")) @@ -304,15 +317,6 @@ bool VersionFile::hasJarMods() void VersionFile::applyTo(MinecraftProfile *version) { - if (minimumLauncherVersion != -1) - { - if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) - { - throw LauncherVersionError(minimumLauncherVersion, - CURRENT_MINIMUM_LAUNCHER_VERSION); - } - } - if (!version->id.isNull() && !mcVersion.isNull()) { if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) == -- cgit v1.2.3