mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
Wait for MainWindow::close() to be accepted before quitting after the update installer is launched. When the close is vetoed (a running card DB update, open games, or an unsaved deck), keep running and tell the user the installer is already waiting, instead of exiting over their answer. Also fix the comment so it does not claim settings are saved on the vetoed path.
276 lines
11 KiB
C++
276 lines
11 KiB
C++
#include "dlg_update.h"
|
|
|
|
#include "../../../client/settings/cache_settings.h"
|
|
#include "../client/network/update/client/client_update_checker.h"
|
|
#include "../client/network/update/client/release_channel.h"
|
|
#include "../interface/window_main.h"
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDesktopServices>
|
|
#include <QLabel>
|
|
#include <QMessageBox>
|
|
#include <QProgressBar>
|
|
#include <QPushButton>
|
|
#include <QTimer>
|
|
#include <QVBoxLayout>
|
|
#include <QtNetwork>
|
|
#include <version_string.h>
|
|
|
|
DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent)
|
|
{
|
|
|
|
// Handle layout
|
|
statusLabel = new QLabel(this);
|
|
statusLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
|
|
statusLabel->setWordWrap(true);
|
|
descriptionLabel =
|
|
new QLabel(tr("Current release channel") +
|
|
QString(": %1").arg(tr(SettingsCache::instance().getUpdateReleaseChannel()->getName().toUtf8())),
|
|
this);
|
|
progress = new QProgressBar(this);
|
|
|
|
buttonBox = new QDialogButtonBox(this);
|
|
buttonBox->setFixedWidth(350);
|
|
|
|
ok = new QPushButton("Close", this);
|
|
manualDownload = new QPushButton(tr("Reinstall"), this);
|
|
stopDownload = new QPushButton(tr("Cancel Download"), this);
|
|
gotoDownload = new QPushButton(tr("Open Download Page"), this);
|
|
|
|
addStopDownloadAndRemoveOthers(false); // Add all buttons to box
|
|
enableUpdateButton(false); // Unless we know there's an update available, you can't install
|
|
buttonBox->addButton(ok, QDialogButtonBox::AcceptRole);
|
|
|
|
connect(gotoDownload, &QPushButton::clicked, this, &DlgUpdate::gotoDownloadPage);
|
|
//! \todo Make reinstall button actually do something when clicked.
|
|
// connect(manualDownload, &QPushButton::clicked, this, &DlgUpdate::downloadUpdate);
|
|
connect(stopDownload, &QPushButton::clicked, this, &DlgUpdate::cancelDownload);
|
|
connect(ok, &QPushButton::clicked, this, &DlgUpdate::closeDialog);
|
|
|
|
auto *parentLayout = new QVBoxLayout(this);
|
|
parentLayout->addWidget(descriptionLabel);
|
|
parentLayout->addWidget(statusLabel);
|
|
parentLayout->addWidget(progress);
|
|
parentLayout->addWidget(buttonBox);
|
|
|
|
setLayout(parentLayout);
|
|
setWindowTitle(tr("Check for Client Updates"));
|
|
|
|
setFixedHeight(this->sizeHint().height());
|
|
setFixedWidth(this->sizeHint().width());
|
|
|
|
// Check for SSL (this probably isn't necessary)
|
|
if (!QSslSocket::supportsSsl()) {
|
|
enableUpdateButton(false);
|
|
QMessageBox::critical(this, tr("Error"),
|
|
tr("Cockatrice was not built with SSL support, therefore you cannot download updates "
|
|
"automatically! \nPlease visit the download page to update manually."));
|
|
}
|
|
|
|
// Initialize the checker and downloader class
|
|
uDownloader = new UpdateDownloader(this);
|
|
connect(uDownloader, &UpdateDownloader::downloadSuccessful, this, &DlgUpdate::downloadSuccessful);
|
|
connect(uDownloader, &UpdateDownloader::progressMade, this, &DlgUpdate::downloadProgressMade);
|
|
connect(uDownloader, &UpdateDownloader::error, this, &DlgUpdate::downloadError);
|
|
|
|
// Check for updates
|
|
beginUpdateCheck();
|
|
}
|
|
|
|
void DlgUpdate::closeDialog()
|
|
{
|
|
accept();
|
|
}
|
|
|
|
void DlgUpdate::gotoDownloadPage()
|
|
{
|
|
QDesktopServices::openUrl(SettingsCache::instance().getUpdateReleaseChannel()->getManualDownloadUrl());
|
|
}
|
|
|
|
void DlgUpdate::downloadUpdate(const QString &releaseName)
|
|
{
|
|
setLabel(tr("Downloading update: %1").arg(releaseName));
|
|
addStopDownloadAndRemoveOthers(true); // Will remove all other buttons
|
|
uDownloader->beginDownload(updateUrl);
|
|
}
|
|
|
|
void DlgUpdate::cancelDownload()
|
|
{
|
|
emit uDownloader->stopDownload();
|
|
setLabel("Download canceled");
|
|
addStopDownloadAndRemoveOthers(false);
|
|
downloadProgressMade(0, 1);
|
|
}
|
|
|
|
void DlgUpdate::beginUpdateCheck()
|
|
{
|
|
progress->setMinimum(0);
|
|
progress->setMaximum(0);
|
|
setLabel(tr("Checking for updates..."));
|
|
|
|
auto checker = new ClientUpdateChecker(this);
|
|
connect(checker, &ClientUpdateChecker::finishedCheck, this, &DlgUpdate::finishedUpdateCheck);
|
|
connect(checker, &ClientUpdateChecker::error, this, &DlgUpdate::updateCheckError);
|
|
checker->check();
|
|
}
|
|
|
|
void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Release *release)
|
|
{
|
|
|
|
QString publishDate, versionName;
|
|
|
|
// Update the UI to say we've finished
|
|
progress->setMaximum(100);
|
|
setLabel(tr("Finished checking for updates"));
|
|
|
|
// If there are no available builds, then they can't auto update.
|
|
enableUpdateButton(isCompatible);
|
|
|
|
if (isCompatible) {
|
|
// If there is an update, save its URL and work out its name
|
|
updateUrl = release->getDownloadUrl();
|
|
}
|
|
|
|
// Give the user the appropriate message
|
|
if (!needToUpdate) {
|
|
// If there's no need to update, tell them that. However we still allow them to run the
|
|
// downloader themselves if there's a compatible build
|
|
QMessageBox::information(
|
|
window(), tr("No Update Available"),
|
|
tr("Cockatrice is up to date!") + "<br><br>" +
|
|
tr("You are already running the latest version available in the chosen release channel.") + "<br>" +
|
|
"<b>" + tr("Current version") + QString(":</b> %1<br>").arg(VERSION_STRING) + "<b>" +
|
|
tr("Selected release channel") +
|
|
QString(":</b> %1").arg(tr(SettingsCache::instance().getUpdateReleaseChannel()->getName().toUtf8())));
|
|
return;
|
|
}
|
|
|
|
publishDate = release->getPublishDate().toString(QLocale().dateFormat(QLocale::LongFormat));
|
|
if (isCompatible) {
|
|
int reply;
|
|
reply = QMessageBox::question(
|
|
window(), tr("Update Available"),
|
|
tr("A new version of Cockatrice is available!") + "<br><br>" + "<b>" + tr("New version") +
|
|
QString(":</b> %1<br>").arg(release->getName()) + "<b>" + tr("Released") +
|
|
QString(":</b> %1 (<a href=\"%2\">").arg(publishDate, release->getDescriptionUrl()) + tr("Changelog") +
|
|
"</a>)<br><br>" + tr("Do you want to update now?"),
|
|
QMessageBox::Yes | QMessageBox::No);
|
|
|
|
if (reply == QMessageBox::Yes) {
|
|
downloadUpdate(release->getName());
|
|
} else {
|
|
closeDialog();
|
|
}
|
|
} else {
|
|
QMessageBox::information(
|
|
window(), tr("Update Available"),
|
|
tr("A new version of Cockatrice is available!") + "<br><br>" + "<b>" + tr("New version") +
|
|
QString(":</b> %1<br>").arg(release->getName()) + "<b>" + tr("Released") +
|
|
QString(":</b> %1 (<a href=\"%2\">").arg(publishDate, release->getDescriptionUrl()) + tr("Changelog") +
|
|
"</a>)<br><br>" +
|
|
tr("Unfortunately, the automatic updater failed to find a compatible download. \nYou may have to "
|
|
"manually download the new version.") +
|
|
"<br><br>" +
|
|
tr("Please check the <a href=\"%1\">releases page</a> on our Github and download the build for your "
|
|
"system.")
|
|
.arg(release->getDescriptionUrl()));
|
|
}
|
|
}
|
|
|
|
void DlgUpdate::enableUpdateButton(bool enable)
|
|
{
|
|
manualDownload->setEnabled(enable);
|
|
}
|
|
|
|
void DlgUpdate::addStopDownloadAndRemoveOthers(bool enable)
|
|
{
|
|
if (enable) {
|
|
buttonBox->addButton(stopDownload, QDialogButtonBox::ActionRole);
|
|
buttonBox->removeButton(manualDownload);
|
|
buttonBox->removeButton(gotoDownload);
|
|
} else {
|
|
buttonBox->removeButton(stopDownload);
|
|
buttonBox->addButton(manualDownload, QDialogButtonBox::ActionRole);
|
|
buttonBox->addButton(gotoDownload, QDialogButtonBox::ActionRole);
|
|
}
|
|
}
|
|
|
|
void DlgUpdate::enableOkButton(bool enable)
|
|
{
|
|
ok->setEnabled(enable);
|
|
}
|
|
|
|
void DlgUpdate::setLabel(const QString &newText)
|
|
{
|
|
statusLabel->setText(newText);
|
|
}
|
|
|
|
void DlgUpdate::updateCheckError(const QString &errorString)
|
|
{
|
|
setLabel(tr("Error"));
|
|
QMessageBox::critical(this, tr("Update Error"),
|
|
tr("An error occurred while checking for updates:") + QString(" ") + errorString);
|
|
}
|
|
|
|
void DlgUpdate::downloadError(const QString &errorString)
|
|
{
|
|
setLabel(tr("Error"));
|
|
enableUpdateButton(true);
|
|
QMessageBox::critical(this, tr("Update Error"),
|
|
tr("An error occurred while downloading an update:") + QString(" ") + errorString);
|
|
}
|
|
|
|
void DlgUpdate::downloadSuccessful(const QUrl &filepath)
|
|
{
|
|
setLabel(tr("Installing..."));
|
|
|
|
QString installerPath = filepath.toLocalFile();
|
|
|
|
QString appDir = QDir::toNativeSeparators(QCoreApplication::applicationDirPath());
|
|
QProcess process;
|
|
process.setProgram(installerPath);
|
|
|
|
// NSIS needs the /D= argument to be an UNQUOTED string, even if it contains spaces. Qt likes to quote arguments if
|
|
// they contain spaces, so we use the windows exclusive QProcess::setNativeArguments in the only case where this is
|
|
// relevant, which preserves the argument unquoted.
|
|
#ifdef Q_OS_WIN
|
|
process.setNativeArguments(QString("/R /D=%1").arg(appDir));
|
|
#else
|
|
// Linux/macOS: normal argument passing (not relevant since they update differently.)
|
|
process.setArguments({"/R", QString("/D=%1").arg(appDir)});
|
|
#endif
|
|
|
|
// Try to open the installer. If it opens, quit Cockatrice
|
|
if (process.startDetached()) {
|
|
qCInfo(DlgUpdateLog) << "Opened downloaded update file successfully - closing Cockatrice";
|
|
// Close the main window synchronously so file locks are released before the NSIS installer
|
|
// (already launched) starts replacing files. This also flushes settings and shuts down the
|
|
// tabs, but only when the close is actually accepted: MainWindow may veto it for a running
|
|
// card DB update, an open game, or an unsaved deck. In that case keep running so the user
|
|
// can resolve the blocker, and tell them the installer is already waiting.
|
|
if (auto *window = qobject_cast<MainWindow *>(parent())) {
|
|
if (window->close()) {
|
|
QTimer::singleShot(0, qApp, &QCoreApplication::quit);
|
|
} else {
|
|
QMessageBox::warning(this, tr("Update"),
|
|
tr("The update installer is already running and will finish the update once "
|
|
"Cockatrice closes. Cockatrice is still busy, so it stays open for now."));
|
|
}
|
|
} else {
|
|
QTimer::singleShot(0, qApp, &QCoreApplication::quit);
|
|
}
|
|
close();
|
|
} else {
|
|
setLabel(tr("Error"));
|
|
QMessageBox::critical(this, tr("Update Error"),
|
|
tr("Cockatrice is unable to open the installer.") + "<br><br>" +
|
|
tr("Try to update manually by closing Cockatrice and running the installer.") +
|
|
"<br>" + tr("Download location") + QString(": %1").arg(filepath.toLocalFile()));
|
|
}
|
|
}
|
|
|
|
void DlgUpdate::downloadProgressMade(qint64 bytesRead, qint64 totalBytes)
|
|
{
|
|
progress->setMaximum(totalBytes);
|
|
progress->setValue(bytesRead);
|
|
}
|