diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:45:07 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:45:07 +0300 |
| commit | 31b9a8949ed0a288143e23bf739f2eb64fdc63be (patch) | |
| tree | 8a984fa143c38fccad461a77792d6864f3e82cd3 /meshmc/launcher/meta | |
| parent | 934382c8a1ce738589dee9ee0f14e1cec812770e (diff) | |
| parent | fad6a1066616b69d7f5fef01178efdf014c59537 (diff) | |
| download | Project-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.tar.gz Project-Tick-31b9a8949ed0a288143e23bf739f2eb64fdc63be.zip | |
Add 'meshmc/' from commit 'fad6a1066616b69d7f5fef01178efdf014c59537'
git-subtree-dir: meshmc
git-subtree-mainline: 934382c8a1ce738589dee9ee0f14e1cec812770e
git-subtree-split: fad6a1066616b69d7f5fef01178efdf014c59537
Diffstat (limited to 'meshmc/launcher/meta')
| -rw-r--r-- | meshmc/launcher/meta/BaseEntity.cpp | 169 | ||||
| -rw-r--r-- | meshmc/launcher/meta/BaseEntity.h | 79 | ||||
| -rw-r--r-- | meshmc/launcher/meta/Index.cpp | 162 | ||||
| -rw-r--r-- | meshmc/launcher/meta/Index.h | 94 | ||||
| -rw-r--r-- | meshmc/launcher/meta/Index_test.cpp | 68 | ||||
| -rw-r--r-- | meshmc/launcher/meta/JsonFormat.cpp | 239 | ||||
| -rw-r--r-- | meshmc/launcher/meta/JsonFormat.h | 104 | ||||
| -rw-r--r-- | meshmc/launcher/meta/Version.cpp | 153 | ||||
| -rw-r--r-- | meshmc/launcher/meta/Version.h | 140 | ||||
| -rw-r--r-- | meshmc/launcher/meta/VersionList.cpp | 285 | ||||
| -rw-r--r-- | meshmc/launcher/meta/VersionList.h | 121 |
11 files changed, 1614 insertions, 0 deletions
diff --git a/meshmc/launcher/meta/BaseEntity.cpp b/meshmc/launcher/meta/BaseEntity.cpp new file mode 100644 index 0000000000..144d951e75 --- /dev/null +++ b/meshmc/launcher/meta/BaseEntity.cpp @@ -0,0 +1,169 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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 "net/Download.h" +#include "net/HttpMetaCache.h" +#include "net/NetJob.h" +#include "Json.h" + +#include "BuildConfig.h" +#include "Application.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 { + auto doc = Json::requireDocument(data, fname); + auto obj = Json::requireObject(doc, fname); + m_entity->parse(obj); + return true; + } catch (const 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(BuildConfig.META_URL).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 { + auto doc = Json::requireDocument(fname, fname); + auto obj = Json::requireObject(doc, fname); + parse(obj); + return true; + } catch (const 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(Net::Mode loadType) +{ + // 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 (loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) { + return; + } + m_updateTask = + new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()), + APPLICATION->network()); + auto url = this->url(); + auto entry = + APPLICATION->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)); + m_updateTask->addNetAction(dl); + m_updateStatus = UpdateStatus::InProgress; + QObject::connect(m_updateTask.get(), &NetJob::succeeded, [this]() { + m_loadStatus = LoadStatus::Remote; + m_updateStatus = UpdateStatus::Succeeded; + }); + QObject::connect(m_updateTask.get(), &NetJob::failed, + [this]() { m_updateStatus = UpdateStatus::Failed; }); + m_updateTask->start(); +} + +bool Meta::BaseEntity::isLoaded() const +{ + return m_loadStatus > LoadStatus::NotLoaded; +} + +bool Meta::BaseEntity::shouldStartRemoteUpdate() const +{ + // TODO: version-locks and offline mode? + return m_updateStatus != UpdateStatus::InProgress; +} + +Task::Ptr Meta::BaseEntity::getCurrentTask() +{ + if (m_updateStatus == UpdateStatus::InProgress) { + return m_updateTask; + } + return nullptr; +} diff --git a/meshmc/launcher/meta/BaseEntity.h b/meshmc/launcher/meta/BaseEntity.h new file mode 100644 index 0000000000..9fd5b9ad19 --- /dev/null +++ b/meshmc/launcher/meta/BaseEntity.h @@ -0,0 +1,79 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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 "net/Mode.h" +#include "net/NetJob.h" + +namespace Meta +{ + class 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 parse(const QJsonObject& obj) = 0; + + virtual QString localFilename() const = 0; + virtual QUrl url() const; + + bool isLoaded() const; + bool shouldStartRemoteUpdate() const; + + void load(Net::Mode loadType); + Task::Ptr getCurrentTask(); + + protected: /* methods */ + bool loadLocalFile(); + + private: + LoadStatus m_loadStatus = LoadStatus::NotLoaded; + UpdateStatus m_updateStatus = UpdateStatus::NotDone; + NetJob::Ptr m_updateTask; + }; +} // namespace Meta diff --git a/meshmc/launcher/meta/Index.cpp b/meshmc/launcher/meta/Index.cpp new file mode 100644 index 0000000000..ace01c3b84 --- /dev/null +++ b/meshmc/launcher/meta/Index.cpp @@ -0,0 +1,162 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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 std::shared_ptr<Index>& 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()]->mergeFromIndex(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); + }); + } +} // namespace Meta diff --git a/meshmc/launcher/meta/Index.h b/meshmc/launcher/meta/Index.h new file mode 100644 index 0000000000..20809fe15f --- /dev/null +++ b/meshmc/launcher/meta/Index.h @@ -0,0 +1,94 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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" + +class Task; + +namespace Meta +{ + using VersionListPtr = std::shared_ptr<class VersionList>; + using VersionPtr = std::shared_ptr<class Version>; + + class 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 std::shared_ptr<Index>& other); + void parse(const QJsonObject& obj) override; + + private: + QVector<VersionListPtr> m_lists; + QHash<QString, VersionListPtr> m_uids; + + void connectVersionList(const int row, const VersionListPtr& list); + }; +} // namespace Meta diff --git a/meshmc/launcher/meta/Index_test.cpp b/meshmc/launcher/meta/Index_test.cpp new file mode 100644 index 0000000000..221ca7c6e6 --- /dev/null +++ b/meshmc/launcher/meta/Index_test.cpp @@ -0,0 +1,68 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <QTest> +#include "TestUtil.h" + +#include "meta/Index.h" +#include "meta/VersionList.h" + +class IndexTest : public QObject +{ + Q_OBJECT + private slots: + 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/meshmc/launcher/meta/JsonFormat.cpp b/meshmc/launcher/meta/JsonFormat.cpp new file mode 100644 index 0000000000..f624c7d662 --- /dev/null +++ b/meshmc/launcher/meta/JsonFormat.cpp @@ -0,0 +1,239 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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/OneSixVersionFormat.h" +#include "Json.h" + +#include "Index.h" +#include "Version.h" +#include "VersionList.h" + +using namespace Json; + +namespace Meta +{ + + MetadataVersion currentFormatVersion() + { + return MetadataVersion::InitialRelease; + } + + // Index + static std::shared_ptr<Index> 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->setRecommended( + ensureBoolean(obj, QString("recommended"), false)); + version->setVolatile(ensureBoolean(obj, QString("volatile"), false)); + RequireSet requirements, conflicts; + parseRequires(obj, &requirements, "requires"); + parseRequires(obj, &conflicts, "conflicts"); + version->setRequires(requirements, conflicts); + return version; + } + + static std::shared_ptr<Version> 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 std::shared_ptr<VersionList> + 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->setVersions(versions); + return list; + } + + MetadataVersion parseFormatVersion(const QJsonObject& obj, bool required) + { + if (!obj.contains("formatVersion")) { + if (required) { + return MetadataVersion::Invalid; + } + return MetadataVersion::InitialRelease; + } + if (!obj.value("formatVersion").isDouble()) { + return MetadataVersion::Invalid; + } + switch (obj.value("formatVersion").toInt()) { + case 0: + case 1: + return MetadataVersion::InitialRelease; + default: + return MetadataVersion::Invalid; + } + } + + void serializeFormatVersion(QJsonObject& obj, Meta::MetadataVersion version) + { + if (version == MetadataVersion::Invalid) { + return; + } + obj.insert("formatVersion", int(version)); + } + + void parseIndex(const QJsonObject& obj, Index* ptr) + { + const MetadataVersion version = parseFormatVersion(obj); + switch (version) { + case MetadataVersion::InitialRelease: + ptr->merge(parseIndexInternal(obj)); + break; + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); + } + } + + void parseVersionList(const QJsonObject& obj, VersionList* ptr) + { + const MetadataVersion version = parseFormatVersion(obj); + switch (version) { + case MetadataVersion::InitialRelease: + ptr->merge(parseVersionListInternal(obj)); + break; + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); + } + } + + void parseVersion(const QJsonObject& obj, Version* ptr) + { + const MetadataVersion version = parseFormatVersion(obj); + switch (version) { + case MetadataVersion::InitialRelease: + ptr->merge(parseVersionInternal(obj)); + break; + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); + } + } + + /* + [ + {"uid":"foo", "equals":"version"} + ] + */ + void parseRequires(const QJsonObject& obj, RequireSet* ptr, + const char* keyName) + { + if (obj.contains(keyName)) { + QSet<QString> requirements; + auto reqArray = requireArray(obj, keyName); + auto iter = reqArray.begin(); + while (iter != reqArray.end()) { + auto reqObject = requireObject(*iter); + auto uid = requireString(reqObject, "uid"); + auto equals = ensureString(reqObject, "equals", QString()); + auto suggests = ensureString(reqObject, "suggests", QString()); + ptr->insert({uid, equals, suggests}); + iter++; + } + } + } + void serializeRequires(QJsonObject& obj, RequireSet* ptr, + const char* keyName) + { + if (!ptr || ptr->empty()) { + return; + } + QJsonArray arrOut; + for (auto& iter : *ptr) { + QJsonObject reqOut; + reqOut.insert("uid", iter.uid); + if (!iter.equalsVersion.isEmpty()) { + reqOut.insert("equals", iter.equalsVersion); + } + if (!iter.suggests.isEmpty()) { + reqOut.insert("suggests", iter.suggests); + } + arrOut.append(reqOut); + } + obj.insert(keyName, arrOut); + } + +} // namespace Meta diff --git a/meshmc/launcher/meta/JsonFormat.h b/meshmc/launcher/meta/JsonFormat.h new file mode 100644 index 0000000000..933c2391c6 --- /dev/null +++ b/meshmc/launcher/meta/JsonFormat.h @@ -0,0 +1,104 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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" +#include <set> + +namespace Meta +{ + class Index; + class Version; + class VersionList; + + enum class MetadataVersion { Invalid = -1, InitialRelease = 1 }; + + class ParseException : public Exception + { + public: + using Exception::Exception; + }; + struct Require { + bool operator==(const Require& rhs) const + { + return uid == rhs.uid; + } + bool operator<(const Require& rhs) const + { + return uid < rhs.uid; + } + bool deepEquals(const Require& rhs) const + { + return uid == rhs.uid && equalsVersion == rhs.equalsVersion && + suggests == rhs.suggests; + } + QString uid; + QString equalsVersion; + QString suggests; + }; + + inline Q_DECL_PURE_FUNCTION uint qHash(const Require& key, + uint seed = 0) Q_DECL_NOTHROW + { + return qHash(key.uid, seed); + } + + using RequireSet = std::set<Require>; + + void parseIndex(const QJsonObject& obj, Index* ptr); + void parseVersion(const QJsonObject& obj, Version* ptr); + void parseVersionList(const QJsonObject& obj, VersionList* ptr); + + MetadataVersion parseFormatVersion(const QJsonObject& obj, + bool required = true); + void serializeFormatVersion(QJsonObject& obj, MetadataVersion version); + + // FIXME: this has a different shape than the others...FIX IT!? + void parseRequires(const QJsonObject& obj, RequireSet* ptr, + const char* keyName = "requires"); + void serializeRequires(QJsonObject& objOut, RequireSet* ptr, + const char* keyName = "requires"); + MetadataVersion currentFormatVersion(); +} // namespace Meta + +Q_DECLARE_METATYPE(std::set<Meta::Require>) diff --git a/meshmc/launcher/meta/Version.cpp b/meshmc/launcher/meta/Version.cpp new file mode 100644 index 0000000000..f09f5d0a8c --- /dev/null +++ b/meshmc/launcher/meta/Version.cpp @@ -0,0 +1,153 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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/PackProfile.h" + +Meta::Version::Version(const QString& uid, const QString& version) + : BaseVersion(), m_uid(uid), m_version(version) +{ +} + +Meta::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::mergeFromList(const Meta::VersionPtr& other) +{ + if (other->m_providesRecommendations) { + if (m_recommended != other->m_recommended) { + setRecommended(other->m_recommended); + } + } + if (m_type != other->m_type) { + setType(other->m_type); + } + if (m_time != other->m_time) { + setTime(other->m_time); + } + if (m_requires != other->m_requires) { + m_requires = other->m_requires; + } + if (m_conflicts != other->m_conflicts) { + m_conflicts = other->m_conflicts; + } + if (m_volatile != other->m_volatile) { + setVolatile(other->m_volatile); + } +} + +void Meta::Version::merge(const VersionPtr& other) +{ + mergeFromList(other); + if (other->m_data) { + setData(other->m_data); + } +} + +QString Meta::Version::localFilename() const +{ + return m_uid + '/' + m_version + ".json"; +} + +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 Meta::RequireSet& requirements, + const Meta::RequireSet& conflicts) +{ + m_requires = requirements; + m_conflicts = conflicts; + emit requiresChanged(); +} + +void Meta::Version::setVolatile(bool volatile_) +{ + m_volatile = volatile_; +} + +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/meshmc/launcher/meta/Version.h b/meshmc/launcher/meta/Version.h new file mode 100644 index 0000000000..04caedc1c2 --- /dev/null +++ b/meshmc/launcher/meta/Version.h @@ -0,0 +1,140 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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 "JsonFormat.h" + +namespace Meta +{ + using VersionPtr = std::shared_ptr<class Version>; + + class Version : public QObject, public BaseVersion, public BaseEntity + { + Q_OBJECT + + public: /* con/des */ + explicit Version(const QString& uid, const QString& version); + virtual ~Version(); + + QString descriptor() override; + QString name() override; + QString typeString() const override; + + QString uid() const + { + return m_uid; + } + QString version() const + { + return m_version; + } + QString type() const + { + return m_type; + } + QDateTime time() const; + qint64 rawTime() const + { + return m_time; + } + const Meta::RequireSet& requirements() const + { + return m_requires; + } + VersionFilePtr data() const + { + return m_data; + } + bool isRecommended() const + { + return m_recommended; + } + bool isLoaded() const + { + return m_data != nullptr; + } + + void merge(const VersionPtr& other); + void mergeFromList(const VersionPtr& other); + void parse(const QJsonObject& obj) override; + + QString localFilename() const override; + + public: // for usage by format parsers only + void setType(const QString& type); + void setTime(const qint64 time); + void setRequires(const Meta::RequireSet& requirements, + const Meta::RequireSet& conflicts); + void setVolatile(bool volatile_); + 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_version; + QString m_type; + qint64 m_time = 0; + Meta::RequireSet m_requires; + Meta::RequireSet m_conflicts; + bool m_volatile = false; + VersionFilePtr m_data; + }; +} // namespace Meta + +Q_DECLARE_METATYPE(Meta::VersionPtr) diff --git a/meshmc/launcher/meta/VersionList.cpp b/meshmc/launcher/meta/VersionList.cpp new file mode 100644 index 0000000000..1bb150616a --- /dev/null +++ b/meshmc/launcher/meta/VersionList.cpp @@ -0,0 +1,285 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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" +#include "Version.h" + +namespace Meta +{ + VersionList::VersionList(const QString& uid, QObject* parent) + : BaseVersionList(parent), m_uid(uid) + { + setObjectName("Version list: " + uid); + } + + Task::Ptr VersionList::getLoadTask() + { + load(Net::Mode::Online); + 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: { + // FIXME: HACK: this should be generic and be replaced by + // something else. Anything that is a hard 'equals' dep is a + // 'parent uid'. + auto& reqs = version->requirements(); + auto iter = std::find_if(reqs.begin(), reqs.end(), + [](const Require& req) { + return req.uid == "net.minecraft"; + }); + if (iter != reqs.end()) { + return (*iter).equalsVersion; + } + return QVariant(); + } + case TypeRole: + return version->type(); + + case UidRole: + return version->uid(); + case TimeRole: + return version->time(); + case RequiresRole: + return QVariant::fromValue(version->requirements()); + 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)); + } + + // FIXME: this is dumb, we have 'recommended' as part of the metadata + // already... + 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); + } + + // FIXME: this is dumb, we have 'recommended' as part of the metadata + // already... + static const Meta::VersionPtr& getBetterVersion(const Meta::VersionPtr& a, + const Meta::VersionPtr& b) + { + if (!a) + return b; + if (!b) + return a; + if (a->type() == b->type()) { + // newer of same type wins + return (a->rawTime() > b->rawTime() ? a : b); + } + // 'release' type wins + return (a->type() == "release" ? a : b); + } + + void VersionList::mergeFromIndex(const VersionListPtr& other) + { + if (m_name != other->m_name) { + setName(other->m_name); + } + } + + void VersionList::merge(const VersionListPtr& other) + { + if (m_name != other->m_name) { + setName(other->m_name); + } + + // TODO: do not reset the whole model. maybe? + beginResetModel(); + m_versions.clear(); + if (other->m_versions.isEmpty()) { + qWarning() << "Empty list loaded ..."; + } + for (const VersionPtr& version : other->m_versions) { + // we already have the version. merge the contents + if (m_lookup.contains(version->version())) { + m_lookup.value(version->version())->mergeFromList(version); + } else { + m_lookup.insert(version->uid(), version); + } + // connect it. + setupAddedVersion(m_versions.size(), version); + m_versions.append(version); + m_recommended = getBetterVersion(m_recommended, version); + } + endResetModel(); + } + + void VersionList::setupAddedVersion(const int row, + const VersionPtr& version) + { + // FIXME: do not disconnect from everythin, disconnect only the lambdas + // here + version->disconnect(); + 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; + } + +} // namespace Meta diff --git a/meshmc/launcher/meta/VersionList.h b/meshmc/launcher/meta/VersionList.h new file mode 100644 index 0000000000..da903b18e2 --- /dev/null +++ b/meshmc/launcher/meta/VersionList.h @@ -0,0 +1,121 @@ +/* SPDX-FileCopyrightText: 2026 Project Tick + * SPDX-FileContributor: Project Tick + * SPDX-License-Identifier: GPL-3.0-or-later + * + * MeshMC - A Custom Launcher for Minecraft + * Copyright (C) 2026 Project Tick + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2015-2021 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 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 + }; + + Task::Ptr 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 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 setVersions(const QVector<VersionPtr>& versions); + void merge(const VersionListPtr& other); + void mergeFromIndex(const VersionListPtr& other); + 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_name; + + VersionPtr m_recommended; + + void setupAddedVersion(const int row, const VersionPtr& version); + }; +} // namespace Meta +Q_DECLARE_METATYPE(Meta::VersionListPtr) |
