<commit_before>/* Copyright (c) 2013-2021 <NAME>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ApplicationUpdater.h"

#include <QDir>
#include <QFileInfo>

#include "ApplicationUpdatePrompt.h"
#include "ConfigController.h"
#include "GBAApp.h"

#include <mgba/core/version.h>
#include <mgba/feature/updater.h>
#include <mgba-util/table.h>

using namespace QGBA;

ApplicationUpdater::ApplicationUpdater(ConfigController* config, QObject* parent)
	: AbstractUpdater(parent)
	, m_config(config)
	, m_channel(currentChannel())
{
	QVariant lastCheck = config->getQtOption("lastUpdateCheck");
	if (lastCheck.isValid()) {
		m_lastCheck = lastCheck.toDateTime();
	}

	QByteArray bucket(m_config->getOption("update.bucket").toLatin1());
	if (!bucket.isNull()) {
		mUpdate lastUpdate;
		if (mUpdateLoad(m_config->config(), "update.stable", &lastUpdate)) {
			m_updates[QLatin1String("stable")] = UpdateInfo(bucket.constData(), &lastUpdate);
		}
		if (mUpdateLoad(m_config->config(), "update.dev", &lastUpdate)) {
			m_updates[QLatin1String("dev")] = UpdateInfo(bucket.constData(), &lastUpdate);
		}
	}

	connect(this, &AbstractUpdater::updateAvailable, this, [this, config](bool available) {
		m_lastCheck = QDateTime::currentDateTimeUtc();
		config->setQtOption("lastUpdateCheck", m_lastCheck);

		if (available && currentVersion() < updateInfo()) {
			ApplicationUpdatePrompt* prompt = new ApplicationUpdatePrompt;
			connect(prompt, &QDialog::accepted, GBAApp::app(), &GBAApp::restartForUpdate);
			prompt->setAttribute(Qt::WA_DeleteOnClose);
			prompt->show();
		}
	});

	connect(this, &AbstractUpdater::updateDone, this, [this, config]() {
		QByteArray arg0 = GBAApp::app()->arguments().at(0).toUtf8();
		QByteArray path = updateInfo().url.path().toUtf8();
		mUpdateRegister(config->config(), arg0.constData(), path.constData());
		config->write();
	});
}

QUrl ApplicationUpdater::manifestLocation() const {
	return {"https://mgba.io/latest.ini"};
}

QStringList ApplicationUpdater::listChannels() {
	QStringList channels;
	channels << QLatin1String("stable");
	channels << QLatin1String("dev");
	return channels;
}

QString ApplicationUpdater::currentChannel() {
	QLatin1String version(projectVersion);
	QLatin1String branch(gitBranch);
	if (branch == QLatin1String("heads/") + version) {
		return QLatin1String("stable");
	} else {
		return QLatin1String("dev");
	}
}

QString ApplicationUpdater::readableChannel(const QString& channel) {
	if (channel.isEmpty()) {
		return readableChannel(currentChannel());
	}
	if (channel == QLatin1String("stable")) {
		return tr("Stable");
	}
	if (channel == QLatin1String("dev")) {
		return tr("Development");
	}
	return tr("Unknown");
}

ApplicationUpdater::UpdateInfo ApplicationUpdater::currentVersion() {
	UpdateInfo info;
	info.version = QLatin1String(projectVersion);
	info.rev = gitRevision;
	info.commit = QLatin1String(gitCommit);
	return info;
}

QUrl ApplicationUpdater::parseManifest(const QByteArray& manifest) {
	const char* bytes = manifest.constData();

	mUpdaterContext context;
	if (!mUpdaterInit(&context, bytes)) {
		return {};
	}
	m_bucket = QLatin1String(mUpdaterGetBucket(&context));
	m_config->setOption("update.bucket", m_bucket);

	Table updates;
	HashTableInit(&updates, 4, free);
	mUpdaterGetUpdates(&context, platform(), &updates);

	m_updates.clear();
	HashTableEnumerate(&updates, [](const char* key, void* value, void* user) {
		const mUpdate* update = static_cast<mUpdate*>(value);
		ApplicationUpdater* self = static_cast<ApplicationUpdater*>(user);
		self->m_updates[QString::fromUtf8(key)] = UpdateInfo(self->m_bucket, update);
		QByteArray prefix(QString("update.%1").arg(key).toUtf8());
		mUpdateRecord(self->m_config->config(), prefix.constData(), update);
	}, static_cast<void*>(this));

	HashTableDeinit(&updates);
	mUpdaterDeinit(&context);

	if (!m_updates.contains(m_channel)) {
		return {};
	}

	return m_updates[m_channel].url;
}

QString ApplicationUpdater::destination() const {
	QFileInfo path(updateInfo().url.path());
	QDir dir(ConfigController::configDir());
	return dir.filePath(QLatin1String("update.") + path.completeSuffix());
}

const char* ApplicationUpdater::platform() {
#ifdef Q_OS_WIN
	QFileInfo exe(GBAApp::app()->arguments().at(0));
	QFileInfo uninstallInfo(exe.dir().filePath("unins000.dat"));
#ifdef Q_OS_WIN64
	return uninstallInfo.exists() ? "win64-installer" : "win64";
#elif defined(Q_OS_WIN32)
	return uninstallInfo.exists() ? "win32-installer" : "win32";
#endif
#elif defined(Q_OS_MACOS)
	return "osx";<commit_msg>Qt: Show correct channel for modern macOS builds<commit_after>/* Copyright (c) 2013-2021 <NAME>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ApplicationUpdater.h"

#include <QDir>
#include <QFileInfo>

#include "ApplicationUpdatePrompt.h"
#include "ConfigController.h"
#include "GBAApp.h"

#include <mgba/core/version.h>
#include <mgba/feature/updater.h>
#include <mgba-util/table.h>

using namespace QGBA;

ApplicationUpdater::ApplicationUpdater(ConfigController* config, QObject* parent)
	: AbstractUpdater(parent)
	, m_config(config)
	, m_channel(currentChannel())
{
	QVariant lastCheck = config->getQtOption("lastUpdateCheck");
	if (lastCheck.isValid()) {
		m_lastCheck = lastCheck.toDateTime();
	}

	QByteArray bucket(m_config->getOption("update.bucket").toLatin1());
	if (!bucket.isNull()) {
		mUpdate lastUpdate;
		if (mUpdateLoad(m_config->config(), "update.stable", &lastUpdate)) {
			m_updates[QLatin1String("stable")] = UpdateInfo(bucket.constData(), &lastUpdate);
		}
		if (mUpdateLoad(m_config->config(), "update.dev", &lastUpdate)) {
			m_updates[QLatin1String("dev")] = UpdateInfo(bucket.constData(), &lastUpdate);
		}
	}

	connect(this, &AbstractUpdater::updateAvailable, this, [this, config](bool available) {
		m_lastCheck = QDateTime::currentDateTimeUtc();
		config->setQtOption("lastUpdateCheck", m_lastCheck);

		if (available && currentVersion() < updateInfo()) {
			ApplicationUpdatePrompt* prompt = new ApplicationUpdatePrompt;
			connect(prompt, &QDialog::accepted, GBAApp::app(), &GBAApp::restartForUpdate);
			prompt->setAttribute(Qt::WA_DeleteOnClose);
			prompt->show();
		}
	});

	connect(this, &AbstractUpdater::updateDone, this, [this, config]() {
		QByteArray arg0 = GBAApp::app()->arguments().at(0).toUtf8();
		QByteArray path = updateInfo().url.path().toUtf8();
		mUpdateRegister(config->config(), arg0.constData(), path.constData());
		config->write();
	});
}

QUrl ApplicationUpdater::manifestLocation() const {
	return {"https://mgba.io/latest.ini"};
}

QStringList ApplicationUpdater::listChannels() {
	QStringList channels;
	channels << QLatin1String("stable");
	channels << QLatin1String("dev");
	return channels;
}

QString ApplicationUpdater::currentChannel() {
	QLatin1String version(projectVersion);
	QLatin1String branch(gitBranch);
	if (branch == QLatin1String("heads/") + version) {
		return QLatin1String("stable");
	} else {
		return QLatin1String("dev");
	}
}

QString ApplicationUpdater::readableChannel(const QString& channel) {
	if (channel.isEmpty()) {
		return readableChannel(currentChannel());
	}
	if (channel == QLatin1String("stable")) {
		return tr("Stable");
	}
	if (channel == QLatin1String("dev")) {
		return tr("Development");
	}
	return tr("Unknown");
}

ApplicationUpdater::UpdateInfo ApplicationUpdater::currentVersion() {
	UpdateInfo info;
	info.version = QLatin1String(projectVersion);
	info.rev = gitRevision;
	info.commit = QLatin1String(gitCommit);
	return info;
}

QUrl ApplicationUpdater::parseManifest(const QByteArray& manifest) {
	const char* bytes = manifest.constData();

	mUpdaterContext context;
	if (!mUpdaterInit(&context, bytes)) {
		return {};
	}
	m_bucket = QLatin1String(mUpdaterGetBucket(&context));
	m_config->setOption("update.bucket", m_bucket);

	Table updates;
	HashTableInit(&updates, 4, free);
	mUpdaterGetUpdates(&context, platform(), &updates);

	m_updates.clear();
	HashTableEnumerate(&updates, [](const char* key, void* value, void* user) {
		const mUpdate* update = static_cast<mUpdate*>(value);
		ApplicationUpdater* self = static_cast<ApplicationUpdater*>(user);
		self->m_updates[QString::fromUtf8(key)] = UpdateInfo(self->m_bucket, update);
		QByteArray prefix(QString("update.%1").arg(key).toUtf8());
		mUpdateRecord(self->m_config->config(), prefix.constData(), update);
	}, static_cast<void*>(this));

	HashTableDeinit(&updates);
	mUpdaterDeinit(&context);

	if (!m_updates.contains(m_channel)) {
		return {};
	}

	return m_updates[m_channel].url;
}

QString ApplicationUpdater::destination() const {
	QFileInfo path(updateInfo().url.path());
	QDir dir(ConfigController::configDir());
	return dir.filePath(QLatin1String("update.") + path.completeSuffix());
}

const char* ApplicationUpdater::platform() {
#ifdef Q_OS_WIN
	QFileInfo exe(GBAApp::app()->arguments().at(0));
	QFileInfo uninstallInfo(exe.dir().filePath("unins000.dat"));
#ifdef Q_OS_WIN64
	return uninstallInfo.exists() ? "win64-installer" : "win64";
#elif defined(Q_OS_WIN32)
	return uninstallInfo.exists() ? "win32-installer" : "win32";
#endif
#elif defined(Q_OS_MACOS)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
	// Modern macOS build
	return "macos";
#else
	// Legacy "OS X" build
	return "osx";
#endif
{'fraction_non_alphanumeric': 0.15931706295171258, 'fraction_numerical': 0.011521944066198806, 'mean_word_length': 6.471048513302034, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 54, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3854753', 'n_tokens_mistral': 3349, 'n_tokens_neox': 3216, 'n_words': 600}