/* 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 .
*/
#pragma once
#include
#include
#include "net/NetJob.h"
/*!
* Carries all information about an available update that is needed by the
* UpdateController and the UpdateDialog.
*/
struct UpdateAvailableStatus {
/// Normalized version string, e.g. "7.1.0"
QString version;
/// Direct download URL for this platform's artifact (from the feed).
QString downloadUrl;
/// HTML release notes extracted from the feed's element.
QString releaseNotes;
};
Q_DECLARE_METATYPE(UpdateAvailableStatus)
/*!
* UpdateChecker performs the two-source update check used by MeshMC.
*
* Algorithm:
* 1. Download the RSS feed at BuildConfig.UPDATER_FEED_URL in parallel with
* the GitHub releases JSON at BuildConfig.UPDATER_GITHUB_API_URL.
* 2. Parse the latest stable - from the feed → extract
* and the URL whose name contains
* BuildConfig.BUILD_ARTIFACT.
* 3. Parse the GitHub JSON → strip leading "v" from tag_name.
* 4. Both versions must match and be greater than the running version;
* otherwise the check is considered a no-update or failure.
*
* Platform / mode gating (runtime):
* - Linux + APPIMAGE env variable set → updater disabled.
* - Linux + no portable.txt in app dir → updater disabled.
* - Windows / macOS / Linux-portable → updater active.
*/
class UpdateChecker : public QObject
{
Q_OBJECT
public:
explicit UpdateChecker(shared_qobject_ptr nam,
QObject* parent = nullptr);
/*!
* Starts an asynchronous two-source update check.
* If \a notifyNoUpdate is true, noUpdateFound() is emitted when the running
* version is already the latest; otherwise the signal is suppressed.
* Repeated calls while a check is in progress are silently ignored.
*/
void checkForUpdate(bool notifyNoUpdate);
/*!
* Returns true if the updater may run on this platform / installation mode.
* Evaluated at runtime: AppImage detection, portable.txt presence, OS.
* Also checks that BuildConfig.UPDATER_ENABLED is true.
*/
static bool isUpdaterSupported();
signals:
//! Emitted when both sources agree that a newer version is available.
void updateAvailable(UpdateAvailableStatus status);
//! Emitted when the check completes but the running version is current.
void noUpdateFound();
//! Emitted on any network or parse failure.
void checkFailed(QString reason);
private slots:
void onDownloadsFinished(bool notifyNoUpdate);
void onDownloadsFailed(QString reason);
private:
static bool isPortableMode();
static bool isAppImage();
/// Returns current version as "MAJOR.MINOR.HOTFIX".
static QString currentVersion();
/// Strips a leading 'v' and returns a clean X.Y.Z string.
static QString normalizeVersion(const QString& v);
/// Compares two "X.Y.Z" strings numerically. Returns >0 if v1 > v2.
static int compareVersions(const QString& v1, const QString& v2);
shared_qobject_ptr m_network;
NetJob::Ptr m_checkJob;
QByteArray m_feedData;
QByteArray m_githubData;
bool m_checking = false;
};