summaryrefslogtreecommitdiffstats
path: root/api/logic/meta
diff options
context:
space:
mode:
Diffstat (limited to 'api/logic/meta')
-rw-r--r--api/logic/meta/BaseEntity.cpp153
-rw-r--r--api/logic/meta/BaseEntity.h74
-rw-r--r--api/logic/meta/Index.cpp148
-rw-r--r--api/logic/meta/Index.h71
-rw-r--r--api/logic/meta/Index_test.cpp44
-rw-r--r--api/logic/meta/JsonFormat.cpp150
-rw-r--r--api/logic/meta/JsonFormat.h40
-rw-r--r--api/logic/meta/Version.cpp125
-rw-r--r--api/logic/meta/Version.h113
-rw-r--r--api/logic/meta/VersionList.cpp235
-rw-r--r--api/logic/meta/VersionList.h106
11 files changed, 1259 insertions, 0 deletions
diff --git a/api/logic/meta/BaseEntity.cpp b/api/logic/meta/BaseEntity.cpp
new file mode 100644
index 00000000..633afab2
--- /dev/null
+++ b/api/logic/meta/BaseEntity.cpp
@@ -0,0 +1,153 @@
+/* Copyright 2015-2017 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 "BaseEntity.h"
+
+#include "Json.h"
+
+#include "net/Download.h"
+#include "net/HttpMetaCache.h"
+#include "net/NetJob.h"
+
+#include "Env.h"
+#include "Json.h"
+
+class ParsingValidator : public Net::Validator
+{
+public: /* con/des */
+ ParsingValidator(Meta::BaseEntity *entity) : m_entity(entity)
+ {
+ };
+ virtual ~ParsingValidator()
+ {
+ };
+
+public: /* methods */
+ bool init(QNetworkRequest &) override
+ {
+ return true;
+ }
+ bool write(QByteArray & data) override
+ {
+ this->data.append(data);
+ return true;
+ }
+ bool abort() override
+ {
+ return true;
+ }
+ bool validate(QNetworkReply &) override
+ {
+ auto fname = m_entity->localFilename();
+ try
+ {
+ m_entity->parse(Json::requireObject(Json::requireDocument(data, fname), fname));
+ return true;
+ }
+ catch (Exception &e)
+ {
+ qWarning() << "Unable to parse response:" << e.cause();
+ return false;
+ }
+ }
+
+private: /* data */
+ QByteArray data;
+ Meta::BaseEntity *m_entity;
+};
+
+Meta::BaseEntity::~BaseEntity()
+{
+}
+
+QUrl Meta::BaseEntity::url() const
+{
+ return QUrl("https://meta.multimc.org").resolved(localFilename());
+}
+
+bool Meta::BaseEntity::loadLocalFile()
+{
+ const QString fname = QDir("meta").absoluteFilePath(localFilename());
+ if (!QFile::exists(fname))
+ {
+ return false;
+ }
+ // TODO: check if the file has the expected checksum
+ try
+ {
+ parse(Json::requireObject(Json::requireDocument(fname, fname), fname));
+ return true;
+ }
+ catch (Exception &e)
+ {
+ qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause());
+ // just make sure it's gone and we never consider it again.
+ QFile::remove(fname);
+ return false;
+ }
+}
+
+void Meta::BaseEntity::load()
+{
+ // load local file if nothing is loaded yet
+ if(!isLoaded())
+ {
+ if(loadLocalFile())
+ {
+ m_loadStatus = LoadStatus::Local;
+ }
+ }
+ // if we need remote update, run the update task
+ if(!shouldStartRemoteUpdate())
+ {
+ return;
+ }
+ NetJob *job = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()));
+ auto url = this->url();
+ auto entry = ENV.metacache()->resolveEntry("meta", localFilename());
+ entry->setStale(true);
+ auto dl = Net::Download::makeCached(url, entry);
+ /*
+ * The validator parses the file and loads it into the object.
+ * If that fails, the file is not written to storage.
+ */
+ dl->addValidator(new ParsingValidator(this));
+ job->addNetAction(dl);
+ m_updateStatus = UpdateStatus::InProgress;
+ m_updateTask.reset(job);
+ QObject::connect(job, &NetJob::succeeded, [&]()
+ {
+ m_loadStatus = LoadStatus::Remote;
+ m_updateStatus = UpdateStatus::Succeeded;
+ m_updateTask.reset();
+ });
+ QObject::connect(job, &NetJob::failed, [&]()
+ {
+ m_updateStatus = UpdateStatus::Failed;
+ m_updateTask.reset();
+ });
+ m_updateTask->start();
+}
+
+shared_qobject_ptr<Task> Meta::BaseEntity::getCurrentTask()
+{
+ if(m_updateStatus == UpdateStatus::InProgress)
+ {
+ return m_updateTask;
+ }
+ return nullptr;
+}
+
+#include "BaseEntity.moc"
diff --git a/api/logic/meta/BaseEntity.h b/api/logic/meta/BaseEntity.h
new file mode 100644
index 00000000..85051d97
--- /dev/null
+++ b/api/logic/meta/BaseEntity.h
@@ -0,0 +1,74 @@
+/* Copyright 2015-2017 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 <QJsonObject>
+#include <QObject>
+#include "QObjectPtr.h"
+
+#include "multimc_logic_export.h"
+
+class Task;
+namespace Meta
+{
+class MULTIMC_LOGIC_EXPORT BaseEntity
+{
+public: /* types */
+ using Ptr = std::shared_ptr<BaseEntity>;
+ enum class LoadStatus
+ {
+ NotLoaded,
+ Local,
+ Remote
+ };
+ enum class UpdateStatus
+ {
+ NotDone,
+ InProgress,
+ Failed,
+ Succeeded
+ };
+
+public:
+ virtual ~BaseEntity();
+
+ virtual void merge(const std::shared_ptr<BaseEntity> &other) = 0;
+ virtual void parse(const QJsonObject &obj) = 0;
+
+ virtual QString localFilename() const = 0;
+ virtual QUrl url() const;
+
+ bool isLoaded() const
+ {
+ return m_loadStatus > LoadStatus::NotLoaded;
+ }
+ bool shouldStartRemoteUpdate() const
+ {
+ return m_updateStatus == UpdateStatus::NotDone;
+ }
+
+ void load();
+ shared_qobject_ptr<Task> getCurrentTask();
+
+protected: /* methods */
+ bool loadLocalFile();
+
+private:
+ LoadStatus m_loadStatus = LoadStatus::NotLoaded;
+ UpdateStatus m_updateStatus = UpdateStatus::NotDone;
+ shared_qobject_ptr<Task> m_updateTask;
+};
+}
diff --git a/api/logic/meta/Index.cpp b/api/logic/meta/Index.cpp
new file mode 100644
index 00000000..0749651a
--- /dev/null
+++ b/api/logic/meta/Index.cpp
@@ -0,0 +1,148 @@
+/* Copyright 2015-2017 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 "Index.h"
+
+#include "VersionList.h"
+#include "JsonFormat.h"
+
+namespace Meta
+{
+Index::Index(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+Index::Index(const QVector<VersionListPtr> &lists, QObject *parent)
+ : QAbstractListModel(parent), m_lists(lists)
+{
+ for (int i = 0; i < m_lists.size(); ++i)
+ {
+ m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i));
+ connectVersionList(i, m_lists.at(i));
+ }
+}
+
+QVariant Index::data(const QModelIndex &index, int role) const
+{
+ if (index.parent().isValid() || index.row() < 0 || index.row() >= m_lists.size())
+ {
+ return QVariant();
+ }
+
+ VersionListPtr list = m_lists.at(index.row());
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ switch (index.column())
+ {
+ case 0: return list->humanReadable();
+ default: break;
+ }
+ case UidRole: return list->uid();
+ case NameRole: return list->name();
+ case ListPtrRole: return QVariant::fromValue(list);
+ }
+ return QVariant();
+}
+int Index::rowCount(const QModelIndex &parent) const
+{
+ return m_lists.size();
+}
+int Index::columnCount(const QModelIndex &parent) const
+{
+ return 1;
+}
+QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0)
+ {
+ return tr("Name");
+ }
+ else
+ {
+ return QVariant();
+ }
+}
+
+bool Index::hasUid(const QString &uid) const
+{
+ return m_uids.contains(uid);
+}
+
+VersionListPtr Index::get(const QString &uid)
+{
+ VersionListPtr out = m_uids.value(uid, nullptr);
+ if(!out)
+ {
+ out = std::make_shared<VersionList>(uid);
+ m_uids[uid] = out;
+ }
+ return out;
+}
+
+VersionPtr Index::get(const QString &uid, const QString &version)
+{
+ auto list = get(uid);
+ return list->getVersion(version);
+}
+
+void Index::parse(const QJsonObject& obj)
+{
+ parseIndex(obj, this);
+}
+
+void Index::merge(const Ptr &other)
+{
+ const QVector<VersionListPtr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists;
+ // initial load, no need to merge
+ if (m_lists.isEmpty())
+ {
+ beginResetModel();
+ m_lists = lists;
+ for (int i = 0; i < lists.size(); ++i)
+ {
+ m_uids.insert(lists.at(i)->uid(), lists.at(i));
+ connectVersionList(i, lists.at(i));
+ }
+ endResetModel();
+ }
+ else
+ {
+ for (const VersionListPtr &list : lists)
+ {
+ if (m_uids.contains(list->uid()))
+ {
+ m_uids[list->uid()]->merge(list);
+ }
+ else
+ {
+ beginInsertRows(QModelIndex(), m_lists.size(), m_lists.size());
+ connectVersionList(m_lists.size(), list);
+ m_lists.append(list);
+ m_uids.insert(list->uid(), list);
+ endInsertRows();
+ }
+ }
+ }
+}
+
+void Index::connectVersionList(const int row, const VersionListPtr &list)
+{
+ connect(list.get(), &VersionList::nameChanged, this, [this, row]()
+ {
+ emit dataChanged(index(row), index(row), QVector<int>() << Qt::DisplayRole);
+ });
+}
+}
diff --git a/api/logic/meta/Index.h b/api/logic/meta/Index.h
new file mode 100644
index 00000000..9811e152
--- /dev/null
+++ b/api/logic/meta/Index.h
@@ -0,0 +1,71 @@
+/* Copyright 2015-2017 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 <QAbstractListModel>
+#include <memory>
+
+#include "BaseEntity.h"
+
+#include "multimc_logic_export.h"
+
+class Task;
+
+namespace Meta
+{
+using VersionListPtr = std::shared_ptr<class VersionList>;
+using VersionPtr = std::shared_ptr<class Version>;
+
+class MULTIMC_LOGIC_EXPORT Index : public QAbstractListModel, public BaseEntity
+{
+ Q_OBJECT
+public:
+ explicit Index(QObject *parent = nullptr);
+ explicit Index(const QVector<VersionListPtr> &lists, QObject *parent = nullptr);
+
+ enum
+ {
+ UidRole = Qt::UserRole,
+ NameRole,
+ ListPtrRole
+ };
+
+ QVariant data(const QModelIndex &index, int role) const override;
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+
+ QString localFilename() const override { return "index.json"; }
+
+ // queries
+ VersionListPtr get(const QString &uid);
+ VersionPtr get(const QString &uid, const QString &version);
+ bool hasUid(const QString &uid) const;
+
+ QVector<VersionListPtr> lists() const { return m_lists; }
+
+public: // for usage by parsers only
+ void merge(const BaseEntity::Ptr &other) override;
+ void parse(const QJsonObject &obj) override;
+
+private:
+ QVector<VersionListPtr> m_lists;
+ QHash<QString, VersionListPtr> m_uids;
+
+ void connectVersionList(const int row, const VersionListPtr &list);
+};
+}
+
diff --git a/api/logic/meta/Index_test.cpp b/api/logic/meta/Index_test.cpp
new file mode 100644
index 00000000..1c5face2
--- /dev/null
+++ b/api/logic/meta/Index_test.cpp
@@ -0,0 +1,44 @@
+#include <QTest>
+#include "TestUtil.h"
+
+#include "meta/Index.h"
+#include "meta/VersionList.h"
+#include "Env.h"
+
+class IndexTest : public QObject
+{
+ Q_OBJECT
+private
+slots:
+ void test_isProvidedByEnv()
+ {
+ QVERIFY(ENV.metadataIndex());
+ QCOMPARE(ENV.metadataIndex(), ENV.metadataIndex());
+ }
+
+ void test_hasUid_and_getList()
+ {
+ Meta::Index windex({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")});
+ QVERIFY(windex.hasUid("list1"));
+ QVERIFY(!windex.hasUid("asdf"));
+ QVERIFY(windex.get("list2") != nullptr);
+ QCOMPARE(windex.get("list2")->uid(), QString("list2"));
+ QVERIFY(windex.get("adsf") != nullptr);
+ }
+
+ void test_merge()
+ {
+ Meta::Index windex({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")});
+ QCOMPARE(windex.lists().size(), 3);
+ windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")})));
+ QCOMPARE(windex.lists().size(), 3);
+ windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list4"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list5")})));
+ QCOMPARE(windex.lists().size(), 5);
+ windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list6")})));
+ QCOMPARE(windex.lists().size(), 6);
+ }
+};
+
+QTEST_GUILESS_MAIN(IndexTest)
+
+#include "Index_test.moc"
diff --git a/api/logic/meta/JsonFormat.cpp b/api/logic/meta/JsonFormat.cpp
new file mode 100644
index 00000000..fda1cf97
--- /dev/null
+++ b/api/logic/meta/JsonFormat.cpp
@@ -0,0 +1,150 @@
+/* Copyright 2015-2017 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 "JsonFormat.h"
+
+// FIXME: remove this from here... somehow
+#include "minecraft/onesix/OneSixVersionFormat.h"
+#include "Json.h"
+
+#include "Index.h"
+#include "Version.h"
+#include "VersionList.h"
+
+using namespace Json;
+
+namespace Meta
+{
+
+static const int currentFormatVersion = 0;
+
+// Index
+static BaseEntity::Ptr parseIndexInternal(const QJsonObject &obj)
+{
+ const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages");
+ QVector<VersionListPtr> lists;
+ lists.reserve(objects.size());
+ std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj)
+ {
+ VersionListPtr list = std::make_shared<VersionList>(requireString(obj, "uid"));
+ list->setName(ensureString(obj, "name", QString()));
+ return list;
+ });
+ return std::make_shared<Index>(lists);
+}
+
+// Version
+static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
+{
+ VersionPtr version = std::make_shared<Version>(uid, requireString(obj, "version"));
+ version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000);
+ version->setType(ensureString(obj, "type", QString()));
+ version->setParentUid(ensureString(obj, "parentUid", QString()));
+ version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
+ if(obj.contains("requires"))
+ {
+ QHash<QString, QString> requires;
+ auto reqobj = requireObject(obj, "requires");
+ auto iter = reqobj.begin();
+ while(iter != reqobj.end())
+ {
+ requires[iter.key()] = requireString(iter.value());
+ iter++;
+ }
+ version->setRequires(requires);
+ }
+ return version;
+}
+
+static BaseEntity::Ptr parseVersionInternal(const QJsonObject &obj)
+{
+ VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj);
+
+ version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj),
+ QString("%1/%2.json").arg(version->uid(), version->version()),
+ obj.contains("order")));
+ return version;
+}
+
+// Version list / package
+static BaseEntity::Ptr parseVersionListInternal(const QJsonObject &obj)
+{
+ const QString uid = requireString(obj, "uid");
+
+ const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions");
+ QVector<VersionPtr> versions;
+ versions.reserve(versionsRaw.size());
+ std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj)
+ {
+ auto version = parseCommonVersion(uid, vObj);
+ version->setProvidesRecommendations();
+ return version;
+ });
+
+ VersionListPtr list = std::make_shared<VersionList>(uid);
+ list->setName(ensureString(obj, "name", QString()));
+ list->setParentUid(ensureString(obj, "parentUid", QString()));
+ list->setVersions(versions);
+ return list;
+}
+
+
+static int formatVersion(const QJsonObject &obj)
+{
+ if (!obj.contains("formatVersion")) {
+ throw ParseException(QObject::tr("Missing required field: 'formatVersion'"));
+ }
+ if (!obj.value("formatVersion").isDouble()) {
+ throw ParseException(QObject::tr("Required field has invalid type: 'formatVersion'"));
+ }
+ return obj.value("formatVersion").toInt();
+}
+
+void parseIndex(const QJsonObject &obj, Index *ptr)
+{
+ const int version = formatVersion(obj);
+ switch (version) {
+ case 0:
+ ptr->merge(parseIndexInternal(obj));
+ break;
+ default:
+ throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version));
+ }
+}
+
+void parseVersionList(const QJsonObject &obj, VersionList *ptr)
+{
+ const int version = formatVersion(obj);
+ switch (version) {
+ case 0:
+ ptr->merge(parseVersionListInternal(obj));
+ break;
+ default:
+ throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version));
+ }
+}
+
+void parseVersion(const QJsonObject &obj, Version *ptr)
+{
+ const int version = formatVersion(obj);
+ switch (version) {
+ case 0:
+ ptr->merge(parseVersionInternal(obj));
+ break;
+ default:
+ throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version));
+ }
+}
+}
diff --git a/api/logic/meta/JsonFormat.h b/api/logic/meta/JsonFormat.h
new file mode 100644
index 00000000..aaed07fc
--- /dev/null
+++ b/api/logic/meta/JsonFormat.h
@@ -0,0 +1,40 @@
+/* Copyright 2015-2017 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 <QJsonObject>
+#include <memory>
+
+#include "Exception.h"
+#include "meta/BaseEntity.h"
+
+namespace Meta
+{
+class Index;
+class Version;
+class VersionList;
+
+class ParseException : public Exception
+{
+public:
+ using Exception::Exception;
+};
+
+void parseIndex(const QJsonObject &obj, Index *ptr);
+void parseVersion(const QJsonObject &obj, Version *ptr);
+void parseVersionList(const QJsonObject &obj, VersionList *ptr);
+
+}
diff --git a/api/logic/meta/Version.cpp b/api/logic/meta/Version.cpp
new file mode 100644
index 00000000..338e180b
--- /dev/null
+++ b/api/logic/meta/Version.cpp
@@ -0,0 +1,125 @@
+/* Copyright 2015-2017 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 "Version.h"
+
+#include <QDateTime>
+
+#include "JsonFormat.h"
+#include "minecraft/MinecraftProfile.h"
+
+Meta::Version::Version(const QString &uid, const QString &version)
+ : BaseVersion(), m_uid(uid), m_version(version)
+{
+}
+
+QString Meta::Version::descriptor()
+{
+ return m_version;
+}
+QString Meta::Version::name()
+{
+ if(m_data)
+ return m_data->name;
+ return m_uid;
+}
+QString Meta::Version::typeString() const
+{
+ return m_type;
+}
+
+QDateTime Meta::Version::time() const
+{
+ return QDateTime::fromMSecsSinceEpoch(m_time * 1000, Qt::UTC);
+}
+
+void Meta::Version::parse(const QJsonObject& obj)
+{
+ parseVersion(obj, this);
+}
+
+void Meta::Version::merge(const std::shared_ptr<BaseEntity> &other)
+{
+ VersionPtr version = std::dynamic_pointer_cast<Version>(other);
+ if(version->m_providesRecommendations)
+ {
+ if(m_recommended != version->m_recommended)
+ {
+ setRecommended(version->m_recommended);
+ }
+ }
+ if (m_type != version->m_type)
+ {
+ setType(version->m_type);
+ }
+ if (m_time != version->m_time)
+ {
+ setTime(version->m_time);
+ }
+ if (m_requires != version->m_requires)
+ {
+ setRequires(version->m_requires);
+ }
+ if (m_parentUid != version->m_parentUid)
+ {
+ setParentUid(version->m_parentUid);
+ }
+
+ setData(version->m_data);
+}
+
+QString Meta::Version::localFilename() const
+{
+ return m_uid + '/' + m_version + ".json";
+}
+
+void Meta::Version::setParentUid(const QString& parentUid)
+{
+ m_parentUid = parentUid;
+ emit requiresChanged();
+}
+
+void Meta::Version::setType(const QString &type)
+{
+ m_type = type;
+ emit typeChanged();
+}
+
+void Meta::Version::setTime(const qint64 time)
+{
+ m_time = time;
+ emit timeChanged();
+}
+
+void Meta::Version::setRequires(const QHash<QString, QString> &requires)
+{
+ m_requires = requires;
+ emit requiresChanged();
+}
+
+void Meta::Version::setData(const VersionFilePtr &data)
+{
+ m_data = data;
+}
+
+void Meta::Version::setProvidesRecommendations()
+{
+ m_providesRecommendations = true;
+}
+
+void Meta::Version::setRecommended(bool recommended)
+{
+ m_recommended = recommended;
+}
diff --git a/api/logic/meta/Version.h b/api/logic/meta/Version.h
new file mode 100644
index 00000000..f132b861
--- /dev/null
+++ b/api/logic/meta/Version.h
@@ -0,0 +1,113 @@
+/* Copyright 2015-2017 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 "BaseVersion.h"
+
+#include <QJsonObject>
+#include <QStringList>
+#include <QVector>
+#include <memory>
+
+#include "minecraft/VersionFile.h"
+
+#include "BaseEntity.h"
+
+#include "multimc_logic_export.h"
+
+namespace Meta
+{
+using VersionPtr = std::shared_ptr<class Version>;
+
+class MULTIMC_LOGIC_EXPORT Version : public QObject, public BaseVersion, public BaseEntity
+{
+ Q_OBJECT
+
+public: /* con/des */
+ explicit Version(const QString &uid, const QString &version);
+
+ QString descriptor() override;
+ QString name() override;
+ QString typeString() const override;
+
+ QString uid() const
+ {
+ return m_uid;
+ }
+ QString parentUid() const
+ {
+ return m_parentUid;
+ }
+ QString version() const
+ {
+ return m_version;
+ }
+ QString type() const
+ {
+ return m_type;
+ }
+ QDateTime time() const;
+ qint64 rawTime() const
+ {
+ return m_time;
+ }
+ const QHash<QString, QString> &requires() const
+ {
+ return m_requires;
+ }
+ VersionFilePtr data() const
+ {
+ return m_data;
+ }
+ bool isRecommended() const
+ {
+ return m_recommended;
+ }
+
+ void merge(const std::shared_ptr<BaseEntity> &other) override;
+ void parse(const QJsonObject &obj) override;
+
+ QString localFilename() const override;
+
+public: // for usage by format parsers only
+ void setParentUid(const QString &parentUid);
+ void setType(const QString &type);
+ void setTime(const qint64 time);
+ void setRequires(const QHash<QString, QString> &requires);
+ void setRecommended(bool recommended);
+ void setProvidesRecommendations();
+ void setData(const VersionFilePtr &data);
+
+signals:
+ void typeChanged();
+ void timeChanged();
+ void requiresChanged();
+
+private:
+ bool m_providesRecommendations = false;
+ bool m_recommended = false;
+ QString m_name;
+ QString m_uid;
+ QString m_parentUid;
+ QString m_version;
+ QString m_type;
+ qint64 m_time;
+ QHash<QString, QString> m_requires;
+ VersionFilePtr m_data;
+};
+}
+
+Q_DECLARE_METATYPE(Meta::VersionPtr)
diff --git a/api/logic/meta/VersionList.cpp b/api/logic/meta/VersionList.cpp
new file mode 100644
index 00000000..0f1404ba
--- /dev/null
+++ b/api/logic/meta/VersionList.cpp
@@ -0,0 +1,235 @@
+/* Copyright 2015-2017 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 "VersionList.h"
+
+#include <QDateTime>
+
+#include "Version.h"
+#include "JsonFormat.h"
+
+namespace Meta
+{
+VersionList::VersionList(const QString &uid, QObject *parent)
+ : BaseVersionList(parent), m_uid(uid)
+{
+ setObjectName("Version list: " + uid);
+}
+
+shared_qobject_ptr<Task> VersionList::getLoadTask()
+{
+ load();
+ return getCurrentTask();
+}
+
+bool VersionList::isLoaded()
+{
+ return BaseEntity::isLoaded();
+}
+
+const BaseVersionPtr VersionList::at(int i) const
+{
+ return m_versions.at(i);
+}
+int VersionList::count() const
+{
+ return m_versions.size();
+}
+
+void VersionList::sortVersions()
+{
+ beginResetModel();
+ std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
+ {
+ return *a.get() < *b.get();
+ });
+ endResetModel();
+}
+
+QVariant VersionList::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() < 0 || index.row() >= m_versions.size() || index.parent().isValid())
+ {
+ return QVariant();
+ }
+
+ VersionPtr version = m_versions.at(index.row());
+
+ switch (role)
+ {
+ case VersionPointerRole: return QVariant::fromValue(std::dynamic_pointer_cast<BaseVersion>(version));
+ case VersionRole:
+ case VersionIdRole:
+ return version->version();
+ case ParentVersionRole:
+ {
+ auto parentUid = this->parentUid();
+ if(parentUid.isEmpty())
+ {
+ return QVariant();
+ }
+ auto & reqs = version->requires();
+ auto iter = reqs.find(parentUid);
+ if (iter != reqs.end())
+ {
+ return iter.value();
+ }
+ }
+ case TypeRole: return version->type();
+
+ case UidRole: return version->uid();
+ case TimeRole: return version->time();
+ case RequiresRole: return QVariant::fromValue(version->requires());
+ case SortRole: return version->rawTime();
+ case VersionPtrRole: return QVariant::fromValue(version);
+ case RecommendedRole: return version->isRecommended();
+ // FIXME: this should be determined in whatever view/proxy is used...
+ // case LatestRole: return version == getLatestStable();
+ default: return QVariant();
+ }
+}
+
+BaseVersionList::RoleList VersionList::providesRoles() const
+{
+ return {VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole,
+ TypeRole, UidRole, TimeRole, RequiresRole, SortRole,
+ RecommendedRole, LatestRole, VersionPtrRole};
+}
+
+QHash<int, QByteArray> VersionList::roleNames() const
+{
+ QHash<int, QByteArray> roles = BaseVersionList::roleNames();
+ roles.insert(UidRole, "uid");
+ roles.insert(TimeRole, "time");
+ roles.insert(SortRole, "sort");
+ roles.insert(RequiresRole, "requires");
+ return roles;
+}
+
+QString VersionList::localFilename() const
+{
+ return m_uid + "/index.json";
+}
+
+QString VersionList::humanReadable() const
+{
+ return m_name.isEmpty() ? m_uid : m_name;
+}
+
+VersionPtr VersionList::getVersion(const QString &version)
+{
+ VersionPtr out = m_lookup.value(version, nullptr);
+ if(!out)
+ {
+ out = std::make_shared<Version>(m_uid, version);
+ m_lookup[version] = out;
+ }
+ return out;
+}
+
+void VersionList::setName(const QString &name)
+{
+ m_name = name;
+ emit nameChanged(name);
+}
+
+void VersionList::setVersions(const QVector<VersionPtr> &versions)
+{
+ beginResetModel();
+ m_versions = versions;
+ std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
+ {
+ return a->rawTime() > b->rawTime();
+ });
+ for (int i = 0; i < m_versions.size(); ++i)
+ {
+ m_lookup.insert(m_versions.at(i)->version(), m_versions.at(i));
+ setupAddedVersion(i, m_versions.at(i));
+ }
+
+ auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; });
+ m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt;
+ endResetModel();
+}
+
+void VersionList::parse(const QJsonObject& obj)
+{
+ parseVersionList(obj, this);
+}
+
+void VersionList::merge(const BaseEntity::Ptr &other)
+{
+ const VersionListPtr list = std::dynamic_pointer_cast<VersionList>(other);
+ if (m_name != list->m_name)
+ {
+ setName(list->m_name);
+ }
+
+ if(m_parentUid != list->m_parentUid)
+ {
+ setParentUid(list->m_parentUid);
+ }
+
+ if (m_versions.isEmpty())
+ {
+ setVersions(list->m_versions);
+ }
+ else
+ {
+ for (const VersionPtr &version : list->m_versions)
+ {
+ if (m_lookup.contains(version->version()))
+ {
+ m_lookup.value(version->version())->merge(version);
+ }
+ else
+ {
+ beginInsertRows(QModelIndex(), m_versions.size(), m_versions.size());
+ setupAddedVersion(m_versions.size(), version);
+ m_versions.append(version);
+ m_lookup.insert(version->uid(), version);
+ endInsertRows();
+
+ if (!m_recommended || (version->type() == "release" && version->rawTime() > m_recommended->rawTime()))
+ {
+ m_recommended = version;
+ emit dataChanged(index(0), index(m_versions.size() - 1), QVector<int>() << RecommendedRole);
+ }
+ }
+ }
+ }
+}
+
+void VersionList::setupAddedVersion(const int row, const VersionPtr &version)
+{
+ connect(version.get(), &Version::requiresChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); });
+ connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TimeRole << SortRole); });
+ connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); });
+}
+
+BaseVersionPtr VersionList::getRecommended() const
+{
+ return m_recommended;
+}
+
+}
+
+void Meta::VersionList::setParentUid(const QString& parentUid)
+{
+ m_parentUid = parentUid;
+}
+
+
+#include "VersionList.moc"
diff --git a/api/logic/meta/VersionList.h b/api/logic/meta/VersionList.h
new file mode 100644
index 00000000..e8016314
--- /dev/null
+++ b/api/logic/meta/VersionList.h
@@ -0,0 +1,106 @@
+/* Copyright 2015-2017 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 "BaseEntity.h"
+#include "BaseVersionList.h"
+#include <QJsonObject>
+#include <memory>
+
+namespace Meta
+{
+using VersionPtr = std::shared_ptr<class Version>;
+using VersionListPtr = std::shared_ptr<class VersionList>;
+
+class MULTIMC_LOGIC_EXPORT VersionList : public BaseVersionList, public BaseEntity
+{
+ Q_OBJECT
+ Q_PROPERTY(QString uid READ uid CONSTANT)
+ Q_PROPERTY(QString name READ name NOTIFY nameChanged)
+public:
+ explicit VersionList(const QString &uid, QObject *parent = nullptr);
+
+ enum Roles
+ {
+ UidRole = Qt::UserRole + 100,
+ TimeRole,
+ RequiresRole,
+ VersionPtrRole
+ };
+
+ shared_qobject_ptr<Task> getLoadTask() override;
+ bool isLoaded() override;
+ const BaseVersionPtr at(int i) const override;
+ int count() const override;
+ void sortVersions() override;
+
+ BaseVersionPtr getRecommended() const override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+ RoleList providesRoles() const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ QString localFilename() const override;
+
+ QString parentUid() const
+ {
+ return m_parentUid;
+ }
+ QString uid() const
+ {
+ return m_uid;
+ }
+ QString name() const
+ {
+ return m_name;
+ }
+ QString humanReadable() const;
+
+ VersionPtr getVersion(const QString &version);
+
+ QVector<VersionPtr> versions() const
+ {
+ return m_versions;
+ }
+
+public: // for usage only by parsers
+ void setName(const QString &name);
+ void setParentUid(const QString &parentUid);
+ void setVersions(const QVector<VersionPtr> &versions);
+ void merge(const BaseEntity::Ptr &other) override;
+ void parse(const QJsonObject &obj) override;
+
+signals:
+ void nameChanged(const QString &name);
+
+protected slots:
+ void updateListData(QList<BaseVersionPtr>) override
+ {
+ }
+
+private:
+ QVector<VersionPtr> m_versions;
+ QHash<QString, VersionPtr> m_lookup;
+ QString m_uid;
+ QString m_parentUid;
+ QString m_name;
+
+ VersionPtr m_recommended;
+
+ void setupAddedVersion(const int row, const VersionPtr &version);
+};
+}
+Q_DECLARE_METATYPE(Meta::VersionListPtr)