X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=src%2Fwin_updater.cpp;h=10fee22765549089e2be1df485bdfc1e1295d9de;hb=413c93a4586c8366bcf2c94180f4cd746fe49644;hp=68472614946b809cd9369ae03e3df1b22c5e5c44;hpb=21b57e4a426b9b0c90930bfbac247d971f95ad13;p=x264-launcher%2Fx264-launcher.git diff --git a/src/win_updater.cpp b/src/win_updater.cpp index 6847261..10fee22 100644 --- a/src/win_updater.cpp +++ b/src/win_updater.cpp @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// // Simple x264 Launcher -// Copyright (C) 2004-2013 LoRd_MuldeR +// Copyright (C) 2004-2022 LoRd_MuldeR // // 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 @@ -20,105 +20,151 @@ /////////////////////////////////////////////////////////////////////////////// #include "win_updater.h" -#include "uic_win_updater.h" +#include "UIC_win_updater.h" +//Internal #include "global.h" -#include "checksum.h" +#include "model_sysinfo.h" +//MUtils +#include +#include +#include +#include +#include + +//Qt #include #include #include #include +#include +#include +#include +#include +#include +#include +#include /////////////////////////////////////////////////////////////////////////////// +static const char *const DIGEST_KEY = "~Dv/bW3/7t>6?RXVwkaZk-hmS0#O4JS/5YQAO>\\8hvr0B~7[n!X~KMYruemu:MDq"; + +const UpdaterDialog::binary_t UpdaterDialog::BINARIES[] = +{ + { "curl.exe", "02f8831c1c93733daf46a4fb183499bc463aa6555214a193937036a1a279e31a65dacef20b4f3b542de5304608688437421a3b4d82cd6b851a245fb5c4f888d1", 1 }, + { "curl.crt", "e2942301ec0aa5dc82ea8a193ec0537359891ae3eeddf67643cd9c84ce5c4120c58d70c0416208b614e5383b758975091c2e18006251ea5504583b4bff0fbbc6", 0 }, + { "vrfy.exe", "91dd35a9d223c42c4c39d8b1ef928c1f0354125a4eefff46a5984082990b3505c272e8d1a4d73b9089ec9c27fb055dab2560a5ebb1d15f323be6615ca0e176d0", 1 }, + { "wupd.exe", "018a8d0d848407fb0cb530b4540c6f025fd4c280885becd37f83feed8aeb3af6f8e8e0d45066a36549efac7e64706ac1ef09aaa5c75ab8d12c4a70f41518a894", 1 }, + { NULL, NULL, 0 } +}; + #define UPDATE_TEXT(N, TEXT) ui->label_phase##N->setText((TEXT)) #define UPDATE_ICON(N, ICON) ui->icon_phase##N->setPixmap(QIcon(":/buttons/" ICON ".png").pixmap(16, 16)) #define SHOW_ANIMATION(FLAG) do \ { \ - ui->labelLoadingLeft->setVisible((FLAG)); \ - ui->labelLoadingCenter->setVisible((FLAG)); \ - ui->labelLoadingRight->setVisible((FLAG)); \ + ui->frameAnimation->setVisible((FLAG)); \ ui->labelInfo->setVisible(!(FLAG)); \ ui->labelUrl->setVisible(!(FLAG)); \ + ui->labelBuildNo->setVisible(!(FLAG)); \ + if((FLAG)) m_animator->start(); else m_animator->stop(); \ } \ while(0) +static inline QString getBin(const QMap> &binaries, const QString &nameName) +{ + const QSharedPointer file = binaries.value(nameName); + return file.isNull() ? QString() : file->fileName(); +} + +static void qFileDeleter(QFile *const file) +{ + if(file) + { + file->close(); + delete file; + } +} /////////////////////////////////////////////////////////////////////////////// // Constructor & Destructor /////////////////////////////////////////////////////////////////////////////// -UpdaterDialog::UpdaterDialog(QWidget *parent) +UpdaterDialog::UpdaterDialog(QWidget *parent, const SysinfoModel *sysinfo, const char *const updateUrl) : QDialog(parent), ui(new Ui::UpdaterDialog()), - m_state(0), + m_sysinfo(sysinfo), + m_updateUrl(updateUrl), + m_status(MUtils::UpdateChecker::UpdateStatus_NotStartedYet), + m_thread(NULL), + m_updaterProcess(NULL), + m_success(false), m_firstShow(true) { //Init the dialog, from the .ui file ui->setupUi(this); setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); - //Fix size + //Scale and fix size + MUtils::GUI::scale_widget(this); setFixedSize(size()); + //Enable buttons + connect(ui->buttonCancel, SIGNAL(clicked()), this, SLOT(close())); + connect(ui->buttonDownload, SIGNAL(clicked()), this, SLOT(installUpdate())); + connect(ui->buttonRetry, SIGNAL(clicked()), this, SLOT(checkForUpdates())); + + //Enable info label + connect(ui->labelUrl, SIGNAL(linkActivated(QString)), this, SLOT(openUrl(QString))); + //Init animation - m_animator = new QMovie(":/images/loading.gif"); - ui->labelLoadingCenter->setMovie(m_animator); - m_animator->start(); + m_animator.reset(new QMovie(":/images/loading.gif")); + ui->labelLoadingCenter->setMovie(m_animator.data()); //Init buttons ui->buttonCancel->setEnabled(false); ui->buttonRetry->hide(); ui->buttonDownload->hide(); + ui->labelCancel->hide(); - //Hide labels - ui->labelInfo->hide(); - ui->labelUrl->hide(); - - /* - //TEST - QBlake2Checksum checksum; - checksum.update("The quick brown fox jumps over the lazy dog"); - qWarning("Result: %s\n", checksum.finalize().constData()); - - //TEST - QBlake2Checksum checksum2; - QFile file("G:\\Aktorwerkstoffe.2013-11-22.rar"); - if(file.open(QIODevice::ReadOnly)) - { - checksum2.update(file); - qWarning("Result: %s\n", checksum2.finalize().constData()); - } - */ - - QMessageBox::information(this, "Disclaimer", "Welcome to the auto-updater mockup demo!"); + //Start animation + SHOW_ANIMATION(true); } UpdaterDialog::~UpdaterDialog(void) { - X264_DELETE(m_animator); + if(!m_thread.isNull()) + { + if(!m_thread->wait(5000)) + { + m_thread->terminate(); + m_thread->wait(); + } + } delete ui; } /////////////////////////////////////////////////////////////////////////////// -// Public Functions -/////////////////////////////////////////////////////////////////////////////// - -/*None yet*/ - -/////////////////////////////////////////////////////////////////////////////// // Events /////////////////////////////////////////////////////////////////////////////// +bool UpdaterDialog::event(QEvent *e) +{ + if((e->type() == QEvent::ActivationChange) && (m_updaterProcess != NULL)) + { + MUtils::GUI::bring_to_front(m_updaterProcess); + } + return QDialog::event(e); +} + void UpdaterDialog::showEvent(QShowEvent *event) { if(m_firstShow) { m_firstShow = false; - QTimer::singleShot(0, this, SLOT(initUpdate())); + QTimer::singleShot(16, this, SLOT(initUpdate())); } } @@ -130,14 +176,108 @@ void UpdaterDialog::closeEvent(QCloseEvent *e) } } +void UpdaterDialog::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) + { + case Qt::Key_Escape: + if ((!m_thread.isNull()) && m_thread->isRunning()) + { + if (m_status >= MUtils::UpdateChecker::UpdateStatus_FetchingUpdates) + { + UPDATE_TEXT(2, tr("Cancellation requested...")); + } + else + { + UPDATE_TEXT(1, tr("Cancellation requested...")); + } + m_thread->cancel(); + } + break; + case Qt::Key_F11: + { + const QString logFilePath = MUtils::make_temp_file(MUtils::temp_folder(), "txt", true); + if (!logFilePath.isEmpty()) + { + qWarning("Write log to: '%s'", MUTILS_UTF8(logFilePath)); + QFile logFile(logFilePath); + if (logFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + logFile.write("\xEF\xBB\xBF"); + for (QStringList::ConstIterator iter = m_logFile.constBegin(); iter != m_logFile.constEnd(); iter++) + { + logFile.write(iter->toUtf8()); + logFile.write("\r\n"); + } + logFile.close(); + QDesktopServices::openUrl(QUrl::fromLocalFile(logFile.fileName())); + } + } + } + break; + default: + QDialog::keyPressEvent(event); + } +} + /////////////////////////////////////////////////////////////////////////////// // Slots /////////////////////////////////////////////////////////////////////////////// void UpdaterDialog::initUpdate(void) { - //Restet text + //Check binary files + if(!checkBinaries()) + { + ui->buttonCancel->setEnabled(true); + const QString message = QString("%1

%3
").arg(tr("At least one file required by the web-update tool is missing or corrupted.
Please re-install this application and then try again!"), QString::fromLatin1(m_updateUrl), QString::fromLatin1(m_updateUrl).replace("-", "−")); + if(QMessageBox::critical(this, tr("File Error"), message, tr("Download Latest Version"), tr("Discard")) == 0) + { + QDesktopServices::openUrl(QUrl(QString::fromLatin1(m_updateUrl))); + } + close(); + return; + } + + //Make sure user does have admin access + if(!MUtils::OS::user_is_admin()) + { + qWarning("User is not in the \"admin\" group, cannot update!"); + QString message; + message += QString("%1
").arg(tr("Sorry, but only users in the \"Administrators\" group can install updates.")); + message += QString("%1").arg(tr("Please start application from an administrator account and try again!")); + if(QMessageBox::critical(this, this->windowTitle(), message, tr("Discard"), tr("Ignore")) != 1) + { + ui->buttonCancel->setEnabled(true); + close(); + return; + } + } + + //Create and setup thread + if(!m_thread) + { + m_thread.reset(new MUtils::UpdateChecker(getBin(m_binaries, "curl.exe"), getBin(m_binaries, "vrfy.exe"), "Simple x264 Launcher", x264_version_build(), false)); + connect(m_thread.data(), SIGNAL(statusChanged(int)), this, SLOT(threadStatusChanged(int))); + connect(m_thread.data(), SIGNAL(finished()), this, SLOT(threadFinished())); + connect(m_thread.data(), SIGNAL(terminated()), this, SLOT(threadFinished())); + connect(m_thread.data(), SIGNAL(messageLogged(QString)), this, SLOT(threadMessageLogged(QString))); + } + + //Begin updater run + QTimer::singleShot(16, this, SLOT(checkForUpdates())); +} + +void UpdaterDialog::checkForUpdates(void) +{ + if((!m_thread) || m_thread->isRunning()) + { + qWarning("Update in progress, cannot check for updates now!"); + } + + //Clear texts ui->retranslateUi(this); + ui->labelBuildNo->setText(tr("Installed build is #%1 | Latest build is #%2").arg(QString::number(x264_version_build()), tr("N/A"))); //Init buttons ui->buttonCancel->setEnabled(false); @@ -147,47 +287,292 @@ void UpdaterDialog::initUpdate(void) //Hide labels ui->labelInfo->hide(); ui->labelUrl->hide(); + ui->labelCancel->show(); - //Reset icons - UPDATE_ICON(1, "clock"); - UPDATE_ICON(2, "clock"); - UPDATE_ICON(3, "clock"); + //Update status + threadStatusChanged(MUtils::UpdateChecker::UpdateStatus_NotStartedYet); - //Show animation + //Start animation SHOW_ANIMATION(true); - //Begin updater test run - m_state = 0; - QTimer::singleShot(333, this, SLOT(updateState())); + //Update cursor + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + QApplication::setOverrideCursor(Qt::WaitCursor); + + //Clear log + m_logFile.clear(); + + //Init timer + m_elapsed.reset(new QElapsedTimer()); + m_elapsed->start(); + + //Start the updater thread + QTimer::singleShot(125, m_thread.data(), SLOT(start())); } -void UpdaterDialog::updateState(void) +void UpdaterDialog::threadStatusChanged(int status) { - switch(m_state++) + const int prevStatus = m_status; + switch(m_status = status) { - case 0: + case MUtils::UpdateChecker::UpdateStatus_NotStartedYet: + UPDATE_ICON(1, "clock"); + UPDATE_ICON(2, "clock"); + UPDATE_ICON(3, "clock"); + break; + case MUtils::UpdateChecker::UpdateStatus_CheckingConnection: UPDATE_ICON(1, "play"); - QTimer::singleShot(6666, this, SLOT(updateState())); break; - case 1: + case MUtils::UpdateChecker::UpdateStatus_FetchingUpdates: UPDATE_ICON(1, "shield_green"); UPDATE_TEXT(1, tr("Internet connection is working.")); UPDATE_ICON(2, "play"); - QTimer::singleShot(6666, this, SLOT(updateState())); break; - case 2: + case MUtils::UpdateChecker::UpdateStatus_ErrorNoConnection: + UPDATE_ICON(1, "shield_error"); + UPDATE_TEXT(1, tr("Computer is currently offline!")); + UPDATE_ICON(2, "shield_grey"); + UPDATE_ICON(3, "shield_grey"); + break; + case MUtils::UpdateChecker::UpdateStatus_ErrorConnectionTestFailed: + UPDATE_ICON(1, "shield_error"); + UPDATE_TEXT(1, tr("Internet connectivity test failed!")); + UPDATE_ICON(2, "shield_grey"); + UPDATE_ICON(3, "shield_grey"); + break; + case MUtils::UpdateChecker::UpdateStatus_ErrorFetchUpdateInfo: + UPDATE_ICON(2, "shield_error"); + UPDATE_TEXT(2, tr("Failed to download the update information!")); + UPDATE_ICON(3, "shield_grey"); + break; + case MUtils::UpdateChecker::UpdateStatus_CompletedUpdateAvailable: + case MUtils::UpdateChecker::UpdateStatus_CompletedNoUpdates: + case MUtils::UpdateChecker::UpdateStatus_CompletedNewVersionOlder: UPDATE_ICON(2, "shield_green"); - UPDATE_TEXT(2, tr("Update-information was received successfully.")); + UPDATE_TEXT(2, tr("Update information received successfully.")); UPDATE_ICON(3, "play"); - QTimer::singleShot(6666, this, SLOT(updateState())); break; - case 3: - UPDATE_ICON(3, "shield_exclamation"); - UPDATE_TEXT(3, tr("A newer version is available!")); - QTimer::singleShot(6666, this, SLOT(updateState())); + case MUtils::UpdateChecker::UpdateStatus_CancelledByUser: + if (prevStatus >= MUtils::UpdateChecker::UpdateStatus_FetchingUpdates) + { + UPDATE_ICON(2, "shield_error"); + UPDATE_TEXT(2, tr("Operation was cancelled by the user!")); + UPDATE_ICON(3, "shield_grey"); + } + else + { + UPDATE_ICON(1, "shield_error"); + UPDATE_TEXT(1, tr("Operation was cancelled by the user!")); + UPDATE_ICON(2, "shield_grey"); + UPDATE_ICON(3, "shield_grey"); + } + break; + default: + MUTILS_THROW("Unknown status code!"); + } +} + +void UpdaterDialog::threadFinished(void) +{ + m_success = m_thread->getSuccess(); + ui->labelCancel->hide(); + QTimer::singleShot((m_success ? 500 : 0), this, SLOT(updateFinished())); +} + +void UpdaterDialog::updateFinished(void) +{ + //Query the timer, if available + if (!m_elapsed.isNull()) + { + const quint64 elapsed = m_elapsed->restart(); + qDebug("Update check completed after %.2f seconds.", double(elapsed) / 1000.0); + } + + //Restore cursor + QApplication::restoreOverrideCursor(); + + //If update was successfull, process final updater state + if(m_thread->getSuccess()) + { + switch(m_status) + { + case MUtils::UpdateChecker::UpdateStatus_CompletedUpdateAvailable: + UPDATE_ICON(3, "shield_exclamation"); + UPDATE_TEXT(3, tr("A newer version is available!")); + ui->buttonDownload->show(); + break; + case MUtils::UpdateChecker::UpdateStatus_CompletedNoUpdates: + UPDATE_ICON(3, "shield_green"); + UPDATE_TEXT(3, tr("Your version is up-to-date.")); + break; + case MUtils::UpdateChecker::UpdateStatus_CompletedNewVersionOlder: + UPDATE_ICON(3, "shield_blue"); + UPDATE_TEXT(3, tr("You are using a pre-release version!")); + break; + default: + qWarning("Update thread succeeded with unexpected status code: %d", m_status); + } + } + + //Show update info or retry button + switch(m_status) + { + case MUtils::UpdateChecker::UpdateStatus_CompletedUpdateAvailable: + case MUtils::UpdateChecker::UpdateStatus_CompletedNoUpdates: + case MUtils::UpdateChecker::UpdateStatus_CompletedNewVersionOlder: + SHOW_ANIMATION(false); + ui->labelBuildNo->setText(tr("Installed build is #%1 | Latest build is #%2").arg(QString::number(x264_version_build()), QString::number(m_thread->getUpdateInfo()->getBuildNo()))); + ui->labelUrl->setText(QString("%1").arg(m_thread->getUpdateInfo()->getDownloadSite())); + break; + case MUtils::UpdateChecker::UpdateStatus_ErrorNoConnection: + case MUtils::UpdateChecker::UpdateStatus_ErrorConnectionTestFailed: + case MUtils::UpdateChecker::UpdateStatus_ErrorFetchUpdateInfo: + case MUtils::UpdateChecker::UpdateStatus_CancelledByUser: + m_animator->stop(); + ui->buttonRetry->show(); + break; + default: + qWarning("Update thread finished with unexpected status code: %d", m_status); + } + + //Re-enbale cancel button + ui->buttonCancel->setEnabled(true); + +} + +void UpdaterDialog::threadMessageLogged(const QString &message) +{ + m_logFile << message; +} + +void UpdaterDialog::openUrl(const QString &url) +{ + qDebug("Open URL: %s", url.toLatin1().constData()); + QDesktopServices::openUrl(QUrl(url)); +} + +void UpdaterDialog::installUpdate(void) +{ + if(!((m_thread) && m_thread->getSuccess())) + { + qWarning("Cannot download/install update at this point!"); + return; + } + + QApplication::setOverrideCursor(Qt::WaitCursor); + ui->buttonDownload->hide(); + ui->buttonCancel->setEnabled(false); + SHOW_ANIMATION(true); + + const MUtils::UpdateCheckerInfo *updateInfo = m_thread->getUpdateInfo(); + + QProcess process; + QStringList args; + QEventLoop loop; + + MUtils::init_process(process, MUtils::temp_folder(), false); + + connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); + connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit())); + + args << QString("/Location=%1").arg(updateInfo->getDownloadAddress()); + args << QString("/Filename=%1").arg(updateInfo->getDownloadFilename()); + args << QString("/TicketID=%1").arg(updateInfo->getDownloadFilecode()); + args << QString("/CheckSum=%1").arg(updateInfo->getDownloadChecksum()); + args << QString("/ToFolder=%1").arg(QDir::toNativeSeparators(QDir(QApplication::applicationDirPath()).canonicalPath())); + args << QString("/ToExFile=%1.exe").arg(QFileInfo(QFileInfo(QApplication::applicationFilePath()).canonicalFilePath()).completeBaseName()); + args << QString("/AppTitle=Simple x264 Launcher (Build #%1)").arg(QString::number(updateInfo->getBuildNo())); + + process.start(getBin(m_binaries, "wupd.exe"), args); + if(!process.waitForStarted()) + { + QApplication::restoreOverrideCursor(); SHOW_ANIMATION(false); - ui->buttonCancel->setEnabled(true); + QMessageBox::critical(this, tr("Update Failed"), tr("Sorry, failed to launch web-update program!")); ui->buttonDownload->show(); - break; + ui->buttonCancel->setEnabled(true); + return; + } + + m_updaterProcess = MUtils::OS::process_id(&process); + loop.exec(QEventLoop::ExcludeUserInputEvents); + + if(!process.waitForFinished()) + { + process.kill(); + process.waitForFinished(); + } + + m_updaterProcess = NULL; + QApplication::restoreOverrideCursor(); + ui->buttonDownload->show(); + ui->buttonCancel->setEnabled(true); + SHOW_ANIMATION(false); + + if(process.exitCode() == 0) + { + done(READY_TO_INSTALL_UPDATE); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Private Functions +/////////////////////////////////////////////////////////////////////////////// + +bool UpdaterDialog::checkBinaries(void) +{ + qDebug("[File Verification]"); + for(size_t i = 0; BINARIES[i].name; i++) + { + const QString name = QString::fromLatin1(BINARIES[i].name); + if (!m_binaries.contains(name)) + { + QScopedPointer binary(new QFile(QString("%1/toolset/common/%2").arg(m_sysinfo->getAppPath(), name))); + if (binary->open(QIODevice::ReadOnly)) + { + if (checkFileHash(binary->fileName(), BINARIES[i].hash)) + { + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + m_binaries.insert(name, QSharedPointer(binary.take(), qFileDeleter)); + } + else + { + qWarning("Verification of '%s' has failed!", MUTILS_UTF8(name)); + binary->close(); + return false; + } + } + else + { + qWarning("File '%s' could not be opened!", MUTILS_UTF8(name)); + return false; + } + } + } + qDebug("File check completed.\n"); + return true; +} + +bool UpdaterDialog::checkFileHash(const QString &filePath, const char *expectedHash) +{ + qDebug("Checking file: %s", MUTILS_UTF8(filePath)); + QScopedPointer checksum(MUtils::Hash::create(MUtils::Hash::HASH_BLAKE2_512, DIGEST_KEY)); + QFile file(filePath); + if(file.open(QIODevice::ReadOnly)) + { + checksum->update(file); + const QByteArray fileHash = checksum->digest(); + if((strlen(expectedHash) != fileHash.size()) || (memcmp(fileHash.constData(), expectedHash, fileHash.size()) != 0)) + { + qWarning("\nFile appears to be corrupted:\n%s\n", filePath.toUtf8().constData()); + qWarning("Expected Hash: %s\nDetected Hash: %s\n", expectedHash, fileHash.constData()); + return false; + } + return true; + } + else + { + qWarning("Failed to open file:\n%s\n", filePath.toUtf8().constData()); + return false; } }