Move all the things (YES. Move them.)

Also, implemented some basic modlist logic, to be wired up.
This commit is contained in:
Petr Mrázek
2013-08-17 13:40:51 +02:00
parent 77e8066542
commit 253067c782
184 changed files with 414 additions and 345 deletions

View 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;
}

View 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;
};

View 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;
}
}
}

View 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;
};

View 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);
}

View 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();
};

View 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()
{
}

View 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;
};