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/net/HttpMetaCache.cpp | |
| 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/net/HttpMetaCache.cpp')
| -rw-r--r-- | meshmc/launcher/net/HttpMetaCache.cpp | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/meshmc/launcher/net/HttpMetaCache.cpp b/meshmc/launcher/net/HttpMetaCache.cpp new file mode 100644 index 0000000000..8e1f69d63d --- /dev/null +++ b/meshmc/launcher/net/HttpMetaCache.cpp @@ -0,0 +1,283 @@ +/* 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 2013-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 "HttpMetaCache.h" +#include "FileSystem.h" + +#include <QFileInfo> +#include <QFile> +#include <QDateTime> +#include <QCryptographicHash> + +#include <QDebug> + +#include <QJsonDocument> +#include <QJsonArray> +#include <QJsonObject> + +QString MetaEntry::getFullPath() +{ + // FIXME: make local? + return FS::PathCombine(basePath, relativePath); +} + +HttpMetaCache::HttpMetaCache(QString path) : QObject() +{ + m_index_file = path; + saveBatchingTimer.setSingleShot(true); + saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); + connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow())); +} + +HttpMetaCache::~HttpMetaCache() +{ + saveBatchingTimer.stop(); + SaveNow(); +} + +MetaEntryPtr HttpMetaCache::getEntry(QString base, QString resource_path) +{ + // no base. no base path. can't store + if (!m_entries.contains(base)) { + // TODO: log problem + return MetaEntryPtr(); + } + EntryMap& map = m_entries[base]; + if (map.entry_list.contains(resource_path)) { + return map.entry_list[resource_path]; + } + return MetaEntryPtr(); +} + +MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, + QString expected_etag) +{ + auto entry = getEntry(base, resource_path); + // it's not present? generate a default stale entry + if (!entry) { + return staleEntry(base, resource_path); + } + + auto& selected_base = m_entries[base]; + QString real_path = FS::PathCombine(selected_base.base_path, resource_path); + QFileInfo finfo(real_path); + + // is the file really there? if not -> stale + if (!finfo.isFile() || !finfo.isReadable()) { + // if the file doesn't exist, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + + if (!expected_etag.isEmpty() && expected_etag != entry->etag) { + // if the etag doesn't match expected, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + + // if the file changed, check md5sum + qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); + if (file_last_changed != entry->local_changed_timestamp) { + QFile input(real_path); + if (!input.open(QIODevice::ReadOnly)) + return staleEntry(base, resource_path); + QString md5sum = + QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5) + .toHex() + .constData(); + if (entry->md5sum != md5sum) { + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + // md5sums matched... keep entry and save the new state to file + entry->local_changed_timestamp = file_last_changed; + SaveEventually(); + } + + // entry passed all the checks we cared about. + entry->basePath = getBasePath(base); + return entry; +} + +bool HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) +{ + if (!m_entries.contains(stale_entry->baseId)) { + qCritical() << "Cannot add entry with unknown base: " + << stale_entry->baseId.toLocal8Bit(); + return false; + } + if (stale_entry->stale) { + qCritical() << "Cannot add stale entry: " + << stale_entry->getFullPath().toLocal8Bit(); + return false; + } + m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = + stale_entry; + SaveEventually(); + return true; +} + +bool HttpMetaCache::evictEntry(MetaEntryPtr entry) +{ + if (entry) { + entry->stale = true; + SaveEventually(); + return true; + } + return false; +} + +MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path) +{ + auto foo = new MetaEntry(); + foo->baseId = base; + foo->basePath = getBasePath(base); + foo->relativePath = resource_path; + foo->stale = true; + return MetaEntryPtr(foo); +} + +void HttpMetaCache::addBase(QString base, QString base_root) +{ + // TODO: report error + if (m_entries.contains(base)) + return; + // TODO: check if the base path is valid + EntryMap foo; + foo.base_path = base_root; + m_entries[base] = foo; +} + +QString HttpMetaCache::getBasePath(QString base) +{ + if (m_entries.contains(base)) { + return m_entries[base].base_path; + } + return QString(); +} + +void HttpMetaCache::Load() +{ + if (m_index_file.isNull()) + return; + + QFile index(m_index_file); + if (!index.open(QIODevice::ReadOnly)) + return; + + QJsonDocument json = QJsonDocument::fromJson(index.readAll()); + if (!json.isObject()) + return; + auto root = json.object(); + // check file version first + auto version_val = root.value("version"); + if (!version_val.isString()) + return; + if (version_val.toString() != "1") + return; + + // read the entry array + auto entries_val = root.value("entries"); + if (!entries_val.isArray()) + return; + QJsonArray array = entries_val.toArray(); + for (auto element : array) { + if (!element.isObject()) + return; + auto element_obj = element.toObject(); + QString base = element_obj.value("base").toString(); + if (!m_entries.contains(base)) + continue; + auto& entrymap = m_entries[base]; + auto foo = new MetaEntry(); + foo->baseId = base; + QString path = foo->relativePath = element_obj.value("path").toString(); + foo->md5sum = element_obj.value("md5sum").toString(); + foo->etag = element_obj.value("etag").toString(); + foo->local_changed_timestamp = + element_obj.value("last_changed_timestamp").toDouble(); + foo->remote_changed_timestamp = + element_obj.value("remote_changed_timestamp").toString(); + // presumed innocent until closer examination + foo->stale = false; + entrymap.entry_list[path] = MetaEntryPtr(foo); + } +} + +void HttpMetaCache::SaveEventually() +{ + // reset the save timer + saveBatchingTimer.stop(); + saveBatchingTimer.start(30000); +} + +void HttpMetaCache::SaveNow() +{ + if (m_index_file.isNull()) + return; + QJsonObject toplevel; + toplevel.insert("version", QJsonValue(QString("1"))); + QJsonArray entriesArr; + for (auto group : m_entries) { + for (auto entry : group.entry_list) { + // do not save stale entries. they are dead. + if (entry->stale) { + continue; + } + QJsonObject entryObj; + entryObj.insert("base", QJsonValue(entry->baseId)); + entryObj.insert("path", QJsonValue(entry->relativePath)); + entryObj.insert("md5sum", QJsonValue(entry->md5sum)); + entryObj.insert("etag", QJsonValue(entry->etag)); + entryObj.insert("last_changed_timestamp", + QJsonValue(double(entry->local_changed_timestamp))); + if (!entry->remote_changed_timestamp.isEmpty()) + entryObj.insert("remote_changed_timestamp", + QJsonValue(entry->remote_changed_timestamp)); + entriesArr.append(entryObj); + } + } + toplevel.insert("entries", entriesArr); + + QJsonDocument doc(toplevel); + try { + FS::write(m_index_file, doc.toJson()); + } catch (const Exception& e) { + qWarning() << e.what(); + } +} |
