Move all the things (YES. Move them.)
Also, implemented some basic modlist logic, to be wired up.
This commit is contained in:
129
logic/lists/InstVersionList.cpp
Normal file
129
logic/lists/InstVersionList.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
/* 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 "logic/lists/InstVersionList.h"
|
||||
#include "logic/InstanceVersion.h"
|
||||
|
||||
InstVersionList::InstVersionList(QObject *parent) :
|
||||
QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
InstVersionPtr InstVersionList::findVersion( const QString& descriptor )
|
||||
{
|
||||
for (int i = 0; i < count(); i++)
|
||||
{
|
||||
if (at(i)->descriptor == descriptor)
|
||||
return at(i);
|
||||
}
|
||||
return InstVersionPtr();
|
||||
}
|
||||
|
||||
InstVersionPtr InstVersionList::getLatestStable() const
|
||||
{
|
||||
if (count() <= 0)
|
||||
return InstVersionPtr();
|
||||
else
|
||||
return at(0);
|
||||
}
|
||||
|
||||
QVariant InstVersionList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
|
||||
|
||||
InstVersionPtr version = at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case NameColumn:
|
||||
return version->name;
|
||||
|
||||
case TypeColumn:
|
||||
return version->typeString();
|
||||
|
||||
case TimeColumn:
|
||||
return version->timestamp;
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
return version->descriptor;
|
||||
|
||||
case VersionPointerRole:
|
||||
return qVariantFromValue(version);
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant InstVersionList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (section)
|
||||
{
|
||||
case NameColumn:
|
||||
return "Name";
|
||||
|
||||
case TypeColumn:
|
||||
return "Type";
|
||||
|
||||
case TimeColumn:
|
||||
return "Time";
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section)
|
||||
{
|
||||
case NameColumn:
|
||||
return "The name of the version.";
|
||||
|
||||
case TypeColumn:
|
||||
return "The version's type.";
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int InstVersionList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
}
|
||||
|
||||
int InstVersionList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 2;
|
||||
}
|
121
logic/lists/InstVersionList.h
Normal file
121
logic/lists/InstVersionList.h
Normal file
@ -0,0 +1,121 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QAbstractListModel>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "logic/InstanceVersion.h"
|
||||
|
||||
class Task;
|
||||
|
||||
/*!
|
||||
* \brief Class that each instance type's version list derives from.
|
||||
* Version lists are the lists that keep track of the available game versions
|
||||
* for that instance. This list will not be loaded on startup. It will be loaded
|
||||
* when the list's load function is called. Before using the version list, you
|
||||
* should check to see if it has been loaded yet and if not, load the list.
|
||||
*
|
||||
* Note that this class also inherits from QAbstractListModel. Methods from that
|
||||
* class determine how this version list shows up in a list view. Said methods
|
||||
* all have a default implementation, but they can be overridden by plugins to
|
||||
* change the behavior of the list.
|
||||
*/
|
||||
class InstVersionList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ModelRoles
|
||||
{
|
||||
VersionPointerRole = 0x34B1CB48
|
||||
};
|
||||
|
||||
enum VListColumns
|
||||
{
|
||||
// First column - Name
|
||||
NameColumn = 0,
|
||||
|
||||
// Second column - Type
|
||||
TypeColumn,
|
||||
|
||||
// Third column - Timestamp
|
||||
TimeColumn
|
||||
};
|
||||
|
||||
explicit InstVersionList(QObject *parent = 0);
|
||||
|
||||
/*!
|
||||
* \brief Gets a task that will reload the version list.
|
||||
* Simply execute the task to load the list.
|
||||
* The task returned by this function should reset the model when it's done.
|
||||
* \return A pointer to a task that reloads the version list.
|
||||
*/
|
||||
virtual Task *getLoadTask() = 0;
|
||||
|
||||
//! Checks whether or not the list is loaded. If this returns false, the list should be loaded.
|
||||
virtual bool isLoaded() = 0;
|
||||
|
||||
//! Gets the version at the given index.
|
||||
virtual const InstVersionPtr at(int i) const = 0;
|
||||
|
||||
//! Returns the number of versions in the list.
|
||||
virtual int count() const = 0;
|
||||
|
||||
|
||||
//////// List Model Functions ////////
|
||||
virtual QVariant data(const QModelIndex &index, int role) const;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
virtual int rowCount(const QModelIndex &parent) const;
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Finds a version by its descriptor.
|
||||
* \param The descriptor of the version to find.
|
||||
* \return A const pointer to the version with the given descriptor. NULL if
|
||||
* one doesn't exist.
|
||||
*/
|
||||
virtual InstVersionPtr findVersion(const QString &descriptor);
|
||||
|
||||
/*!
|
||||
* \brief Gets the latest stable version of this instance type.
|
||||
* This is the version that will be selected by default.
|
||||
* By default, this is simply the first version in the list.
|
||||
*/
|
||||
virtual InstVersionPtr getLatestStable() const;
|
||||
|
||||
/*!
|
||||
* Sorts the version list.
|
||||
*/
|
||||
virtual void sort() = 0;
|
||||
|
||||
protected slots:
|
||||
/*!
|
||||
* Updates this list with the given list of versions.
|
||||
* This is done by copying each version in the given list and inserting it
|
||||
* into this one.
|
||||
* We need to do this so that we can set the parents of the versions are set to this
|
||||
* version list. This can't be done in the load task, because the versions the load
|
||||
* task creates are on the load task's thread and Qt won't allow their parents
|
||||
* to be set to something created on another thread.
|
||||
* To get around that problem, we invoke this method on the GUI thread, which
|
||||
* then copies the versions and sets their parents correctly.
|
||||
* \param versions List of versions whose parents should be set.
|
||||
*/
|
||||
virtual void updateListData(QList<InstVersionPtr > versions) = 0;
|
||||
};
|
232
logic/lists/InstanceList.cpp
Normal file
232
logic/lists/InstanceList.cpp
Normal file
@ -0,0 +1,232 @@
|
||||
/* 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 <QDir>
|
||||
#include <QFile>
|
||||
#include <QDirIterator>
|
||||
#include <QThread>
|
||||
#include <QTextStream>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "logic/lists/InstanceList.h"
|
||||
#include "logic/BaseInstance.h"
|
||||
#include "logic/InstanceFactory.h"
|
||||
|
||||
#include "pathutils.h"
|
||||
|
||||
const static int GROUP_FILE_FORMAT_VERSION = 1;
|
||||
|
||||
InstanceList::InstanceList(const QString &instDir, QObject *parent) :
|
||||
QObject(parent), m_instDir("instances")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void InstanceList::loadGroupList(QMap<QString, QString> & groupMap)
|
||||
{
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
|
||||
// if there's no group file, fail
|
||||
if(!QFileInfo(groupFileName).exists())
|
||||
return;
|
||||
|
||||
QFile groupFile(groupFileName);
|
||||
|
||||
// if you can't open the file, fail
|
||||
if (!groupFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
// An error occurred. Ignore it.
|
||||
qDebug("Failed to read instance group file.");
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream in(&groupFile);
|
||||
QString jsonStr = in.readAll();
|
||||
groupFile.close();
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &error);
|
||||
|
||||
// if the json was bad, fail
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
qWarning(QString("Failed to parse instance group file: %1 at offset %2").
|
||||
arg(error.errorString(), QString::number(error.offset)).toUtf8());
|
||||
return;
|
||||
}
|
||||
|
||||
// if the root of the json wasn't an object, fail
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
qWarning("Invalid group file. Root entry should be an object.");
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject rootObj = jsonDoc.object();
|
||||
|
||||
// Make sure the format version matches, otherwise fail.
|
||||
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
|
||||
return;
|
||||
|
||||
// Get the groups. if it's not an object, fail
|
||||
if (!rootObj.value("groups").isObject())
|
||||
{
|
||||
qWarning("Invalid group list JSON: 'groups' should be an object.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate through all the groups.
|
||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
|
||||
{
|
||||
QString groupName = iter.key();
|
||||
|
||||
// If not an object, complain and skip to the next one.
|
||||
if (!iter.value().isObject())
|
||||
{
|
||||
qWarning(QString("Group '%1' in the group list should "
|
||||
"be an object.").arg(groupName).toUtf8());
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject groupObj = iter.value().toObject();
|
||||
if (!groupObj.value("instances").isArray())
|
||||
{
|
||||
qWarning(QString("Group '%1' in the group list is invalid. "
|
||||
"It should contain an array "
|
||||
"called 'instances'.").arg(groupName).toUtf8());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Iterate through the list of instances in the group.
|
||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||
|
||||
for (QJsonArray::iterator iter2 = instancesArray.begin();
|
||||
iter2 != instancesArray.end(); iter2++)
|
||||
{
|
||||
groupMap[(*iter2).toString()] = groupName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InstanceList::InstListError InstanceList::loadList()
|
||||
{
|
||||
// load the instance groups
|
||||
QMap<QString, QString> groupMap;
|
||||
loadGroupList(groupMap);
|
||||
|
||||
m_instances.clear();
|
||||
QDir dir(m_instDir);
|
||||
QDirIterator iter(dir);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
QString subDir = iter.next();
|
||||
if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists())
|
||||
continue;
|
||||
|
||||
BaseInstance *instPtr = NULL;
|
||||
auto &loader = InstanceFactory::get();
|
||||
auto error = loader.loadInstance(instPtr, subDir);
|
||||
|
||||
switch(error)
|
||||
{
|
||||
case InstanceFactory::NoLoadError:
|
||||
break;
|
||||
case InstanceFactory::NotAnInstance:
|
||||
break;
|
||||
}
|
||||
|
||||
if (error != InstanceFactory::NoLoadError &&
|
||||
error != InstanceFactory::NotAnInstance)
|
||||
{
|
||||
QString errorMsg = QString("Failed to load instance %1: ").
|
||||
arg(QFileInfo(subDir).baseName()).toUtf8();
|
||||
|
||||
switch (error)
|
||||
{
|
||||
default:
|
||||
errorMsg += QString("Unknown instance loader error %1").
|
||||
arg(error);
|
||||
break;
|
||||
}
|
||||
qDebug(errorMsg.toUtf8());
|
||||
}
|
||||
else if (!instPtr)
|
||||
{
|
||||
qDebug(QString("Error loading instance %1. Instance loader returned null.").
|
||||
arg(QFileInfo(subDir).baseName()).toUtf8());
|
||||
}
|
||||
else
|
||||
{
|
||||
QSharedPointer<BaseInstance> inst(instPtr);
|
||||
auto iter = groupMap.find(inst->id());
|
||||
if(iter != groupMap.end())
|
||||
{
|
||||
inst->setGroup((*iter));
|
||||
}
|
||||
qDebug(QString("Loaded instance %1").arg(inst->name()).toUtf8());
|
||||
inst->setParent(this);
|
||||
m_instances.append(inst);
|
||||
connect(instPtr, SIGNAL(propertiesChanged(BaseInstance*)),this, SLOT(propertiesChanged(BaseInstance*)));
|
||||
}
|
||||
}
|
||||
emit invalidated();
|
||||
return NoError;
|
||||
}
|
||||
|
||||
/// Clear all instances. Triggers notifications.
|
||||
void InstanceList::clear()
|
||||
{
|
||||
m_instances.clear();
|
||||
emit invalidated();
|
||||
};
|
||||
|
||||
/// Add an instance. Triggers notifications, returns the new index
|
||||
int InstanceList::add(InstancePtr t)
|
||||
{
|
||||
m_instances.append(t);
|
||||
emit instanceAdded(count() - 1);
|
||||
return count() - 1;
|
||||
}
|
||||
|
||||
InstancePtr InstanceList::getInstanceById(QString instId)
|
||||
{
|
||||
QListIterator<InstancePtr> iter(m_instances);
|
||||
InstancePtr inst;
|
||||
while(iter.hasNext())
|
||||
{
|
||||
inst = iter.next();
|
||||
if (inst->id() == instId)
|
||||
break;
|
||||
}
|
||||
if (inst->id() != instId)
|
||||
return InstancePtr();
|
||||
else
|
||||
return iter.peekPrevious();
|
||||
}
|
||||
|
||||
void InstanceList::propertiesChanged(BaseInstance * inst)
|
||||
{
|
||||
for(int i = 0; i < m_instances.count(); i++)
|
||||
{
|
||||
if(inst == m_instances[i].data())
|
||||
{
|
||||
emit instanceChanged(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
91
logic/lists/InstanceList.h
Normal file
91
logic/lists/InstanceList.h
Normal file
@ -0,0 +1,91 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "logic/BaseInstance.h"
|
||||
|
||||
class BaseInstance;
|
||||
|
||||
class InstanceList : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
/*!
|
||||
* \brief Get the instance groups
|
||||
*/
|
||||
void loadGroupList(QMap<QString, QString> & groupList);
|
||||
|
||||
public:
|
||||
explicit InstanceList(const QString &instDir, QObject *parent = 0);
|
||||
|
||||
/*!
|
||||
* \brief Error codes returned by functions in the InstanceList class.
|
||||
* NoError Indicates that no error occurred.
|
||||
* UnknownError indicates that an unspecified error occurred.
|
||||
*/
|
||||
enum InstListError
|
||||
{
|
||||
NoError = 0,
|
||||
UnknownError
|
||||
};
|
||||
|
||||
QString instDir() const { return m_instDir; }
|
||||
|
||||
/*!
|
||||
* \brief Loads the instance list. Triggers notifications.
|
||||
*/
|
||||
InstListError loadList();
|
||||
|
||||
/*!
|
||||
* \brief Get the instance at index
|
||||
*/
|
||||
InstancePtr at(int i) const
|
||||
{
|
||||
return m_instances.at(i);
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Get the count of loaded instances
|
||||
*/
|
||||
int count() const
|
||||
{
|
||||
return m_instances.count();
|
||||
};
|
||||
|
||||
/// Clear all instances. Triggers notifications.
|
||||
void clear();
|
||||
|
||||
/// Add an instance. Triggers notifications, returns the new index
|
||||
int add(InstancePtr t);
|
||||
|
||||
/// Get an instance by ID
|
||||
InstancePtr getInstanceById (QString id);
|
||||
|
||||
signals:
|
||||
void instanceAdded(int index);
|
||||
void instanceChanged(int index);
|
||||
void invalidated();
|
||||
|
||||
private slots:
|
||||
void propertiesChanged(BaseInstance * inst);
|
||||
|
||||
protected:
|
||||
QString m_instDir;
|
||||
QList< InstancePtr > m_instances;
|
||||
};
|
206
logic/lists/LwjglVersionList.cpp
Normal file
206
logic/lists/LwjglVersionList.cpp
Normal file
@ -0,0 +1,206 @@
|
||||
/* 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 "LwjglVersionList.h"
|
||||
#include "logic/net/NetWorker.h"
|
||||
|
||||
#include <QtNetwork>
|
||||
|
||||
#include <QtXml>
|
||||
|
||||
#include <QRegExp>
|
||||
|
||||
#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss"
|
||||
|
||||
LWJGLVersionList mainVersionList;
|
||||
|
||||
LWJGLVersionList &LWJGLVersionList::get()
|
||||
{
|
||||
return mainVersionList;
|
||||
}
|
||||
|
||||
|
||||
LWJGLVersionList::LWJGLVersionList(QObject *parent) :
|
||||
QAbstractListModel(parent)
|
||||
{
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
|
||||
const PtrLWJGLVersion version = at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
return version->name();
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
return version->url();
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
return "Version";
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
return "LWJGL version name.";
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int LWJGLVersionList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool LWJGLVersionList::isLoading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void LWJGLVersionList::loadList()
|
||||
{
|
||||
Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)");
|
||||
|
||||
setLoading(true);
|
||||
auto & worker = NetWorker::spawn();
|
||||
reply = worker.get(QNetworkRequest(QUrl(RSS_URL)));
|
||||
connect(reply, SIGNAL(finished()), SLOT(netRequestComplete()));
|
||||
}
|
||||
|
||||
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
|
||||
{
|
||||
QDomNodeList elementList = parent.elementsByTagName(tagname);
|
||||
if (elementList.count())
|
||||
return elementList.at(0).toElement();
|
||||
else
|
||||
return QDomElement();
|
||||
}
|
||||
|
||||
void LWJGLVersionList::netRequestComplete()
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NoError)
|
||||
{
|
||||
QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip");
|
||||
Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list",
|
||||
"LWJGL regex is invalid");
|
||||
|
||||
QDomDocument doc;
|
||||
|
||||
QString xmlErrorMsg;
|
||||
int errorLine;
|
||||
if (!doc.setContent(reply->readAll(), false, &xmlErrorMsg, &errorLine))
|
||||
{
|
||||
failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + QString::number(errorLine));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QDomNodeList items = doc.elementsByTagName("item");
|
||||
|
||||
QList<PtrLWJGLVersion> tempList;
|
||||
|
||||
for (int i = 0; i < items.length(); i++)
|
||||
{
|
||||
Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list",
|
||||
"XML element isn't an element... wat?");
|
||||
|
||||
QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link");
|
||||
if (linkElement.isNull())
|
||||
{
|
||||
qWarning() << "Link element" << i << "in RSS feed doesn't exist! Skipping.";
|
||||
continue;
|
||||
}
|
||||
|
||||
QString link = linkElement.text();
|
||||
|
||||
// Make sure it's a download link.
|
||||
if (link.endsWith("/download") && link.contains(lwjglRegex))
|
||||
{
|
||||
QString name = link.mid(lwjglRegex.indexIn(link) + 6);
|
||||
// Subtract 4 here to remove the .zip file extension.
|
||||
name = name.left(lwjglRegex.matchedLength() - 10);
|
||||
|
||||
QUrl url(link);
|
||||
if (!url.isValid())
|
||||
{
|
||||
qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping.";
|
||||
continue;
|
||||
}
|
||||
|
||||
tempList.append(LWJGLVersion::Create(name, link));
|
||||
}
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
m_vlist.swap(tempList);
|
||||
endResetModel();
|
||||
|
||||
qDebug("Loaded LWJGL list.");
|
||||
finished();
|
||||
}
|
||||
else
|
||||
{
|
||||
failed("Failed to load LWJGL list. Network error: " + reply->errorString());
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
const PtrLWJGLVersion LWJGLVersionList::getVersion(const QString &versionName)
|
||||
{
|
||||
for (int i = 0; i < count(); i++)
|
||||
{
|
||||
QString name = at(i)->name();
|
||||
if ( name == versionName)
|
||||
return at(i);
|
||||
}
|
||||
return PtrLWJGLVersion();
|
||||
}
|
||||
|
||||
|
||||
void LWJGLVersionList::failed(QString msg)
|
||||
{
|
||||
qWarning() << msg;
|
||||
emit loadListFailed(msg);
|
||||
}
|
||||
|
||||
void LWJGLVersionList::finished()
|
||||
{
|
||||
emit loadListFinished();
|
||||
}
|
||||
|
||||
void LWJGLVersionList::setLoading(bool loading)
|
||||
{
|
||||
m_loading = loading;
|
||||
emit loadingStateUpdated(m_loading);
|
||||
}
|
117
logic/lists/LwjglVersionList.h
Normal file
117
logic/lists/LwjglVersionList.h
Normal file
@ -0,0 +1,117 @@
|
||||
/* Copyright 2013 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QAbstractListModel>
|
||||
#include <QSharedPointer>
|
||||
#include <QUrl>
|
||||
|
||||
#include <QNetworkReply>
|
||||
|
||||
class LWJGLVersion;
|
||||
typedef QSharedPointer<LWJGLVersion> PtrLWJGLVersion;
|
||||
|
||||
class LWJGLVersion : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
LWJGLVersion(const QString &name, const QString &url, QObject *parent = 0) :
|
||||
QObject(parent), m_name(name), m_url(url) { }
|
||||
public:
|
||||
|
||||
static PtrLWJGLVersion Create(const QString &name, const QString &url, QObject *parent = 0)
|
||||
{
|
||||
return PtrLWJGLVersion(new LWJGLVersion(name, url, parent));
|
||||
};
|
||||
|
||||
QString name() const { return m_name; }
|
||||
|
||||
QString url() const { return m_url; }
|
||||
|
||||
protected:
|
||||
QString m_name;
|
||||
QString m_url;
|
||||
};
|
||||
|
||||
class LWJGLVersionList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LWJGLVersionList(QObject *parent = 0);
|
||||
|
||||
static LWJGLVersionList &get();
|
||||
|
||||
bool isLoaded() { return m_vlist.length() > 0; }
|
||||
|
||||
const PtrLWJGLVersion getVersion(const QString &versionName);
|
||||
PtrLWJGLVersion at(int index) { return m_vlist[index]; }
|
||||
const PtrLWJGLVersion at(int index) const { return m_vlist[index]; }
|
||||
|
||||
int count() const { return m_vlist.length(); }
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role) const;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
virtual int rowCount(const QModelIndex &parent) const { return count(); }
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
|
||||
virtual bool isLoading() const;
|
||||
virtual bool errored() const { return m_errored; }
|
||||
|
||||
virtual QString lastErrorMsg() const { return m_lastErrorMsg; }
|
||||
|
||||
public slots:
|
||||
/*!
|
||||
* Loads the version list.
|
||||
* This is done asynchronously. On success, the loadListFinished() signal will
|
||||
* be emitted. The list model will be reset as well, resulting in the modelReset()
|
||||
* signal being emitted. Note that the model will be reset before loadListFinished() is emitted.
|
||||
* If loading the list failed, the loadListFailed(QString msg),
|
||||
* signal will be emitted.
|
||||
*/
|
||||
virtual void loadList();
|
||||
|
||||
signals:
|
||||
/*!
|
||||
* Emitted when the list either starts or finishes loading.
|
||||
* \param loading Whether or not the list is loading.
|
||||
*/
|
||||
void loadingStateUpdated(bool loading);
|
||||
|
||||
void loadListFinished();
|
||||
|
||||
void loadListFailed(QString msg);
|
||||
|
||||
private:
|
||||
QList<PtrLWJGLVersion> m_vlist;
|
||||
|
||||
QNetworkReply *m_netReply;
|
||||
QNetworkReply *reply;
|
||||
|
||||
bool m_loading;
|
||||
bool m_errored;
|
||||
QString m_lastErrorMsg;
|
||||
|
||||
void failed(QString msg);
|
||||
|
||||
void finished();
|
||||
|
||||
void setLoading(bool loading);
|
||||
|
||||
private slots:
|
||||
virtual void netRequestComplete();
|
||||
};
|
||||
|
300
logic/lists/MinecraftVersionList.cpp
Normal file
300
logic/lists/MinecraftVersionList.cpp
Normal file
@ -0,0 +1,300 @@
|
||||
/* Copyright 2013 Andrew Okin
|
||||
*
|
||||
* 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 "MinecraftVersionList.h"
|
||||
#include <logic/net/NetWorker.h>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <QtXml>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonParseError>
|
||||
|
||||
#include <QtAlgorithms>
|
||||
|
||||
#include <QtNetwork>
|
||||
|
||||
#define MCVLIST_URLBASE "http://s3.amazonaws.com/Minecraft.Download/versions/"
|
||||
#define ASSETS_URLBASE "http://assets.minecraft.net/"
|
||||
#define MCN_URLBASE "http://sonicrules.org/mcnweb.py"
|
||||
|
||||
MinecraftVersionList mcVList;
|
||||
|
||||
MinecraftVersionList::MinecraftVersionList(QObject *parent) :
|
||||
InstVersionList(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Task *MinecraftVersionList::getLoadTask()
|
||||
{
|
||||
return new MCVListLoadTask(this);
|
||||
}
|
||||
|
||||
bool MinecraftVersionList::isLoaded()
|
||||
{
|
||||
return m_loaded;
|
||||
}
|
||||
|
||||
const InstVersionPtr MinecraftVersionList::at(int i) const
|
||||
{
|
||||
return m_vlist.at(i);
|
||||
}
|
||||
|
||||
int MinecraftVersionList::count() const
|
||||
{
|
||||
return m_vlist.count();
|
||||
}
|
||||
|
||||
bool cmpVersions(InstVersionPtr first, InstVersionPtr second)
|
||||
{
|
||||
const InstVersion & left = *first;
|
||||
const InstVersion & right = *second;
|
||||
return left > right;
|
||||
}
|
||||
|
||||
void MinecraftVersionList::sort()
|
||||
{
|
||||
beginResetModel();
|
||||
qSort(m_vlist.begin(), m_vlist.end(), cmpVersions);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
InstVersionPtr MinecraftVersionList::getLatestStable() const
|
||||
{
|
||||
for (int i = 0; i < m_vlist.length(); i++)
|
||||
{
|
||||
auto ver = m_vlist.at(i).dynamicCast<MinecraftVersion>();
|
||||
if (ver->is_latest && !ver->is_snapshot)
|
||||
{
|
||||
return m_vlist.at(i);
|
||||
}
|
||||
}
|
||||
return InstVersionPtr();
|
||||
}
|
||||
|
||||
MinecraftVersionList &MinecraftVersionList::getMainList()
|
||||
{
|
||||
return mcVList;
|
||||
}
|
||||
|
||||
void MinecraftVersionList::updateListData(QList<InstVersionPtr > versions)
|
||||
{
|
||||
beginResetModel();
|
||||
m_vlist = versions;
|
||||
m_loaded = true;
|
||||
endResetModel();
|
||||
// NOW SORT!!
|
||||
sort();
|
||||
}
|
||||
|
||||
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
|
||||
{
|
||||
QDomNodeList elementList = parent.elementsByTagName(tagname);
|
||||
if (elementList.count())
|
||||
return elementList.at(0).toElement();
|
||||
else
|
||||
return QDomElement();
|
||||
}
|
||||
|
||||
inline QDateTime timeFromS3Time(QString str)
|
||||
{
|
||||
return QDateTime::fromString(str, Qt::ISODate);
|
||||
}
|
||||
|
||||
|
||||
MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist)
|
||||
{
|
||||
m_list = vlist;
|
||||
m_currentStable = NULL;
|
||||
vlistReply = nullptr;
|
||||
legacyWhitelist.insert("1.5.2");
|
||||
legacyWhitelist.insert("1.5.1");
|
||||
legacyWhitelist.insert("1.5");
|
||||
legacyWhitelist.insert("1.4.7");
|
||||
legacyWhitelist.insert("1.4.6");
|
||||
legacyWhitelist.insert("1.4.5");
|
||||
legacyWhitelist.insert("1.4.4");
|
||||
legacyWhitelist.insert("1.4.2");
|
||||
legacyWhitelist.insert("1.3.2");
|
||||
legacyWhitelist.insert("1.3.1");
|
||||
legacyWhitelist.insert("1.2.5");
|
||||
legacyWhitelist.insert("1.2.4");
|
||||
legacyWhitelist.insert("1.2.3");
|
||||
legacyWhitelist.insert("1.2.2");
|
||||
legacyWhitelist.insert("1.2.1");
|
||||
legacyWhitelist.insert("1.1");
|
||||
legacyWhitelist.insert("1.0.1");
|
||||
legacyWhitelist.insert("1.0");
|
||||
}
|
||||
|
||||
MCVListLoadTask::~MCVListLoadTask()
|
||||
{
|
||||
}
|
||||
|
||||
void MCVListLoadTask::executeTask()
|
||||
{
|
||||
setStatus("Loading instance version list...");
|
||||
auto & worker = NetWorker::spawn();
|
||||
vlistReply = worker.get(QNetworkRequest(QUrl(QString(MCVLIST_URLBASE) + "versions.json")));
|
||||
connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded()));
|
||||
}
|
||||
|
||||
|
||||
void MCVListLoadTask::list_downloaded()
|
||||
{
|
||||
if(vlistReply->error() != QNetworkReply::QNetworkReply::NoError)
|
||||
{
|
||||
vlistReply->deleteLater();
|
||||
emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError);
|
||||
vlistReply->deleteLater();
|
||||
|
||||
if (jsonError.error != QJsonParseError::NoError)
|
||||
{
|
||||
emitFailed("Error parsing version list JSON:" + jsonError.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
if(!jsonDoc.isObject())
|
||||
{
|
||||
emitFailed("Error parsing version list JSON: jsonDoc is not an object");
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject root = jsonDoc.object();
|
||||
|
||||
// Get the ID of the latest release and the latest snapshot.
|
||||
if(!root.value("latest").isObject())
|
||||
{
|
||||
emitFailed("Error parsing version list JSON: version list is missing 'latest' object");
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject latest = root.value("latest").toObject();
|
||||
|
||||
QString latestReleaseID = latest.value("release").toString("");
|
||||
QString latestSnapshotID = latest.value("snapshot").toString("");
|
||||
if(latestReleaseID.isEmpty())
|
||||
{
|
||||
emitFailed("Error parsing version list JSON: latest release field is missing");
|
||||
return;
|
||||
}
|
||||
if(latestSnapshotID.isEmpty())
|
||||
{
|
||||
emitFailed("Error parsing version list JSON: latest snapshot field is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now, get the array of versions.
|
||||
if(!root.value("versions").isArray())
|
||||
{
|
||||
emitFailed("Error parsing version list JSON: version list object is missing 'versions' array");
|
||||
return;
|
||||
}
|
||||
QJsonArray versions = root.value("versions").toArray();
|
||||
|
||||
QList<InstVersionPtr > tempList;
|
||||
for (int i = 0; i < versions.count(); i++)
|
||||
{
|
||||
bool is_snapshot = false;
|
||||
bool is_latest = false;
|
||||
|
||||
// Load the version info.
|
||||
if(!versions[i].isObject())
|
||||
{
|
||||
//FIXME: log this somewhere
|
||||
continue;
|
||||
}
|
||||
QJsonObject version = versions[i].toObject();
|
||||
QString versionID = version.value("id").toString("");
|
||||
QString versionTimeStr = version.value("releaseTime").toString("");
|
||||
QString versionTypeStr = version.value("type").toString("");
|
||||
if(versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty())
|
||||
{
|
||||
//FIXME: log this somewhere
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the timestamp.
|
||||
QDateTime versionTime = timeFromS3Time(versionTimeStr);
|
||||
if(!versionTime.isValid())
|
||||
{
|
||||
//FIXME: log this somewhere
|
||||
continue;
|
||||
}
|
||||
// Parse the type.
|
||||
MinecraftVersion::VersionType versionType;
|
||||
// OneSix or Legacy. use filter to determine type
|
||||
if (versionTypeStr == "release")
|
||||
{
|
||||
versionType = legacyWhitelist.contains(versionID)?MinecraftVersion::Legacy:MinecraftVersion::OneSix;
|
||||
is_latest = (versionID == latestReleaseID);
|
||||
is_snapshot = false;
|
||||
}
|
||||
else if(versionTypeStr == "snapshot") // It's a snapshot... yay
|
||||
{
|
||||
versionType = legacyWhitelist.contains(versionID)?MinecraftVersion::Legacy:MinecraftVersion::OneSix;
|
||||
is_latest = (versionID == latestSnapshotID);
|
||||
is_snapshot = true;
|
||||
}
|
||||
else if(versionTypeStr == "old_alpha")
|
||||
{
|
||||
versionType = MinecraftVersion::Nostalgia;
|
||||
is_latest = false;
|
||||
is_snapshot = false;
|
||||
}
|
||||
else if(versionTypeStr == "old_beta")
|
||||
{
|
||||
versionType = MinecraftVersion::Legacy;
|
||||
is_latest = false;
|
||||
is_snapshot = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//FIXME: log this somewhere
|
||||
continue;
|
||||
}
|
||||
// Get the download URL.
|
||||
QString dlUrl = QString(MCVLIST_URLBASE) + versionID + "/";
|
||||
|
||||
// Now, we construct the version object and add it to the list.
|
||||
QSharedPointer<MinecraftVersion> mcVersion(new MinecraftVersion());
|
||||
mcVersion->name = mcVersion->descriptor = versionID;
|
||||
mcVersion->timestamp = versionTime.toMSecsSinceEpoch();
|
||||
mcVersion->download_url = dlUrl;
|
||||
mcVersion->is_latest = is_latest;
|
||||
mcVersion->is_snapshot = is_snapshot;
|
||||
mcVersion->type = versionType;
|
||||
tempList.append(mcVersion);
|
||||
}
|
||||
m_list->updateListData(tempList);
|
||||
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: we should have a local cache of the version list and a local cache of version data
|
||||
bool MCVListLoadTask::loadFromVList()
|
||||
{
|
||||
}
|
83
logic/lists/MinecraftVersionList.h
Normal file
83
logic/lists/MinecraftVersionList.h
Normal file
@ -0,0 +1,83 @@
|
||||
/* Copyright 2013 Andrew Okin
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "InstVersionList.h"
|
||||
#include "logic/tasks/Task.h"
|
||||
#include "logic/MinecraftVersion.h"
|
||||
|
||||
class MCVListLoadTask;
|
||||
class QNetworkReply;
|
||||
|
||||
class MinecraftVersionList : public InstVersionList
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
friend class MCVListLoadTask;
|
||||
|
||||
explicit MinecraftVersionList(QObject *parent = 0);
|
||||
|
||||
virtual Task *getLoadTask();
|
||||
virtual bool isLoaded();
|
||||
virtual const InstVersionPtr at(int i) const;
|
||||
virtual int count() const;
|
||||
virtual void sort();
|
||||
|
||||
virtual InstVersionPtr getLatestStable() const;
|
||||
|
||||
/*!
|
||||
* Gets the main version list instance.
|
||||
*/
|
||||
static MinecraftVersionList &getMainList();
|
||||
|
||||
|
||||
protected:
|
||||
QList<InstVersionPtr > m_vlist;
|
||||
|
||||
bool m_loaded;
|
||||
|
||||
protected slots:
|
||||
virtual void updateListData(QList<InstVersionPtr > versions);
|
||||
};
|
||||
|
||||
class MCVListLoadTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MCVListLoadTask(MinecraftVersionList *vlist);
|
||||
~MCVListLoadTask();
|
||||
|
||||
virtual void executeTask();
|
||||
|
||||
protected slots:
|
||||
void list_downloaded();
|
||||
|
||||
protected:
|
||||
//! Loads versions from Mojang's official version list.
|
||||
bool loadFromVList();
|
||||
|
||||
QNetworkReply *vlistReply;
|
||||
MinecraftVersionList *m_list;
|
||||
MinecraftVersion *m_currentStable;
|
||||
QSet<QString> legacyWhitelist;
|
||||
};
|
||||
|
Reference in New Issue
Block a user