GH-1453 report version file problems in the version page

This commit is contained in:
Petr Mrázek 2016-02-21 01:44:27 +01:00
parent 495d320ce2
commit 1a0bbdd9ac
11 changed files with 305 additions and 26 deletions

View File

@ -47,6 +47,51 @@
#include "icons/IconList.h" #include "icons/IconList.h"
#include "Exception.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 QIcon VersionPage::icon() const
{ {
return ENV.icons()->getIcon(m_inst->iconKey()); return ENV.icons()->getIcon(m_inst->iconKey());
@ -72,11 +117,15 @@ VersionPage::VersionPage(OneSixInstance *inst, QWidget *parent)
m_version = m_inst->getMinecraftProfile(); m_version = m_inst->getMinecraftProfile();
if (m_version) 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->installEventFilter(this);
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection); ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &VersionPage::versionCurrent); this, &VersionPage::versionCurrent);
auto smodel = ui->packageView->selectionModel();
connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(packageCurrent(QModelIndex, QModelIndex)));
updateVersionControls(); updateVersionControls();
// select first item. // select first item.
preselect(0); preselect(0);
@ -94,6 +143,49 @@ VersionPage::~VersionPage()
delete ui; delete ui;
} }
void VersionPage::packageCurrent(const QModelIndex &current, 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() void VersionPage::updateVersionControls()
{ {
ui->forgeBtn->setEnabled(true); ui->forgeBtn->setEnabled(true);
@ -453,3 +545,5 @@ void VersionPage::on_revertBtn_clicked()
updateButtons(); updateButtons();
preselect(currentIdx); preselect(currentIdx);
} }
#include "VersionPage.moc"

View File

@ -90,4 +90,6 @@ public slots:
private slots: private slots:
void onGameUpdateError(QString error); void onGameUpdateError(QString error);
void packageCurrent(const QModelIndex &current, const QModelIndex &previous);
}; };

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>693</width> <width>693</width>
<height>575</height> <height>750</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -32,8 +32,8 @@
<attribute name="title"> <attribute name="title">
<string notr="true">Tab 1</string> <string notr="true">Tab 1</string>
</attribute> </attribute>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="0" column="0">
<widget class="ModListView" name="packageView"> <widget class="ModListView" name="packageView">
<property name="verticalScrollBarPolicy"> <property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum> <enum>Qt::ScrollBarAlwaysOn</enum>
@ -49,7 +49,7 @@
</attribute> </attribute>
</widget> </widget>
</item> </item>
<item> <item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
@ -245,6 +245,16 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="1" column="0" colspan="2">
<widget class="MCModInfoFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
@ -263,6 +273,12 @@
<header>widgets/LineSeparator.h</header> <header>widgets/LineSeparator.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>MCModInfoFrame</class>
<extends>QFrame</extends>
<header>widgets/MCModInfoFrame.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>tabWidget</tabstop> <tabstop>tabWidget</tabstop>

View File

@ -46,6 +46,10 @@ void ModListView::setModel ( QAbstractItemModel* model )
head->setStretchLastSection(false); head->setStretchLastSection(false);
// HACK: this is true for the checkbox column of mod lists // HACK: this is true for the checkbox column of mod lists
auto string = model->headerData(0,head->orientation()).toString(); auto string = model->headerData(0,head->orientation()).toString();
if(head->count() < 1)
{
return;
}
if(!string.size()) if(!string.size())
{ {
head->setSectionResizeMode(0, QHeaderView::ResizeToContents); head->setSectionResizeMode(0, QHeaderView::ResizeToContents);

View File

@ -295,6 +295,8 @@ QVariant MinecraftProfile::data(const QModelIndex &index, int role) const
if (row < 0 || row >= VersionPatches.size()) if (row < 0 || row >= VersionPatches.size())
return QVariant(); return QVariant();
auto patch = VersionPatches.at(row);
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
{ {
switch (column) switch (column)
@ -303,7 +305,6 @@ QVariant MinecraftProfile::data(const QModelIndex &index, int role) const
return VersionPatches.at(row)->getPatchName(); return VersionPatches.at(row)->getPatchName();
case 1: case 1:
{ {
auto patch = VersionPatches.at(row);
if(patch->isCustom()) if(patch->isCustom())
{ {
return QString("%1 (Custom)").arg(patch->getPatchVersion()); return QString("%1 (Custom)").arg(patch->getPatchVersion());
@ -317,6 +318,29 @@ QVariant MinecraftProfile::data(const QModelIndex &index, int role) const
return QVariant(); 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(); return QVariant();
} }
QVariant MinecraftProfile::headerData(int section, Qt::Orientation orientation, int role) const QVariant MinecraftProfile::headerData(int section, Qt::Orientation orientation, int role) const

View File

@ -89,12 +89,36 @@ QString MinecraftVersion::getUrl() const
VersionFilePtr MinecraftVersion::getVersionFile() VersionFilePtr MinecraftVersion::getVersionFile()
{ {
QFileInfo versionFile(QString("versions/%1/%1.dat").arg(m_descriptor)); QFileInfo versionFile(QString("versions/%1/%1.dat").arg(m_descriptor));
m_problems.clear();
auto loadedVersionFile = ProfileUtils::parseBinaryJsonFile(versionFile); m_problemSeverity = PROBLEM_NONE;
loadedVersionFile->name = "Minecraft"; if(!versionFile.exists())
//FIXME: possibly not the best place for this... but w/e {
loadedVersionFile->setCustomizable(true); if(m_loadedVersionFile)
return 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() bool MinecraftVersion::isCustomizable()
@ -114,6 +138,24 @@ bool MinecraftVersion::isCustomizable()
return false; return false;
} }
const QList<PatchProblem> &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) void MinecraftVersion::applyTo(MinecraftProfile *version)
{ {
// do we have this one cached? // do we have this one cached?

View File

@ -78,6 +78,9 @@ public: /* methods */
QString getUrl() const; QString getUrl() const;
virtual const QList<PatchProblem> &getProblems() override;
virtual ProblemSeverity getProblemSeverity() override;
private: /* methods */ private: /* methods */
void applyFileTo(MinecraftProfile *version); void applyFileTo(MinecraftProfile *version);
@ -124,4 +127,8 @@ public: /* data */
/// an update available from Mojang /// an update available from Mojang
MinecraftVersionPtr upstreamUpdate; MinecraftVersionPtr upstreamUpdate;
private: /* data */
QDateTime m_loadedVersionFileTimestamp;
mutable VersionFilePtr m_loadedVersionFile;
}; };

View File

@ -111,6 +111,14 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session)
// blatant self-promotion. // blatant self-promotion.
token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; 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(); QString absRootDir = QDir(minecraftRoot()).absolutePath();
token_mapping["game_directory"] = absRootDir; token_mapping["game_directory"] = absRootDir;
@ -119,10 +127,21 @@ QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session)
token_mapping["user_properties"] = session->serializeUserProperties(); token_mapping["user_properties"] = session->serializeUserProperties();
token_mapping["user_type"] = session->user_type; token_mapping["user_type"] = session->user_type;
// 1.7.3+ assets tokens // 1.7.3+ assets tokens
token_mapping["assets_root"] = absAssetsDir; token_mapping["assets_root"] = absAssetsDir;
token_mapping["assets_index_name"] = m_version->assets; 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); QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
for (int i = 0; i < parts.length(); i++) for (int i = 0; i < parts.length(); i++)
{ {

View File

@ -6,6 +6,35 @@
#include "JarMod.h" #include "JarMod.h"
class MinecraftProfile; 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 class ProfilePatch
{ {
public: public:
@ -32,6 +61,31 @@ public:
virtual QString getPatchName() = 0; virtual QString getPatchName() = 0;
virtual QString getPatchVersion() = 0; virtual QString getPatchVersion() = 0;
virtual QString getPatchFilename() = 0; virtual QString getPatchFilename() = 0;
virtual const QList<PatchProblem>& 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<PatchProblem> m_problems;
ProblemSeverity m_problemSeverity = PROBLEM_NONE;
}; };
typedef std::shared_ptr<ProfilePatch> ProfilePatchPtr; typedef std::shared_ptr<ProfilePatch> ProfilePatchPtr;

View File

@ -107,13 +107,26 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder)
.arg(fileInfo.fileName(), file.errorString())); .arg(fileInfo.fileName(), file.errorString()));
} }
QJsonParseError error; QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); auto data = file.readAll();
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) 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( 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(fileInfo.fileName(), error.errorString())
.arg(error.offset)); .arg(line).arg(column));
} }
return VersionFile::fromJson(doc, file.fileName(), requireOrder); return VersionFile::fromJson(doc, file.fileName(), requireOrder);
} }

View File

@ -15,8 +15,6 @@ using namespace Json;
#include "VersionBuildError.h" #include "VersionBuildError.h"
#include <Version.h> #include <Version.h>
#define CURRENT_MINIMUM_LAUNCHER_VERSION 18
static void readString(const QJsonObject &root, const QString &key, QString &variable) static void readString(const QJsonObject &root, const QString &key, QString &variable)
{ {
if (root.contains(key)) if (root.contains(key))
@ -50,6 +48,19 @@ int findLibraryByName(QList<OneSixLibraryPtr> haystack, const GradleSpecifier &n
return retval; 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 VersionFile::fromMojangJson(const QJsonDocument &doc, const QString &filename)
{ {
VersionFilePtr out(new VersionFile()); VersionFilePtr out(new VersionFile());
@ -82,6 +93,7 @@ VersionFilePtr VersionFile::fromMojangJson(const QJsonDocument &doc, const QStri
if (root.contains("minimumLauncherVersion")) if (root.contains("minimumLauncherVersion"))
{ {
out->minimumLauncherVersion = requireInteger(root.value("minimumLauncherVersion")); out->minimumLauncherVersion = requireInteger(root.value("minimumLauncherVersion"));
checkMinimumLauncherVersion(out);
} }
if (root.contains("libraries")) if (root.contains("libraries"))
@ -149,6 +161,7 @@ VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &fi
if (root.contains("minimumLauncherVersion")) if (root.contains("minimumLauncherVersion"))
{ {
out->minimumLauncherVersion = requireInteger(root.value("minimumLauncherVersion")); out->minimumLauncherVersion = requireInteger(root.value("minimumLauncherVersion"));
checkMinimumLauncherVersion(out);
} }
if (root.contains("tweakers")) if (root.contains("tweakers"))
@ -304,15 +317,6 @@ bool VersionFile::hasJarMods()
void VersionFile::applyTo(MinecraftProfile *version) 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 (!version->id.isNull() && !mcVersion.isNull())
{ {
if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) == if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) ==