/* Copyright (C) 2012 Sebastian Herbord. All rights reserved. This file is part of Mod Organizer. Mod Organizer 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. Mod Organizer 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 Mod Organizer. If not, see . */ #include "selfupdater.h" #include "archive.h" #include "callback.h" #include "utility.h" #include "installationmanager.h" #include "iplugingame.h" #include "messagedialog.h" #include "downloadmanager.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "settings.h" #include "bbcode.h" #include "plugincontainer.h" #include "organizercore.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //for VS_FIXEDFILEINFO, GetLastError #include #include #include //for size_t #include using namespace MOBase; using namespace MOShared; typedef Archive* (*CreateArchiveType)(); template static T resolveFunction(QLibrary &lib, const char *name) { T temp = reinterpret_cast(lib.resolve(name)); if (temp == nullptr) { throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); } return temp; } SelfUpdater::SelfUpdater(NexusInterface *nexusInterface) : m_Parent(nullptr) , m_Interface(nexusInterface) , m_Reply(nullptr) , m_Attempts(3) { QLibrary archiveLib(QCoreApplication::applicationDirPath() + "\\dlls\\archive.dll"); if (!archiveLib.load()) { throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); } CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive"); m_ArchiveHandler = CreateArchiveFunc(); if (!m_ArchiveHandler->isValid()) { throw MyException(InstallationManager::getErrorString(m_ArchiveHandler->getLastError())); } m_MOVersion = createVersionInfo(); } SelfUpdater::~SelfUpdater() { delete m_ArchiveHandler; } void SelfUpdater::setUserInterface(QWidget *widget) { m_Parent = widget; } void SelfUpdater::setPluginContainer(PluginContainer *pluginContainer) { m_Interface->setPluginContainer(pluginContainer); } void SelfUpdater::testForUpdate() { // TODO: if prereleases are disabled we could just request the latest release // directly try { m_GitHub.releases(GitHub::Repository("Modorganizer2", "modorganizer"), [this](const QJsonArray &releases) { if (releases.isEmpty()) { qDebug("Unable to connect to github.com to check version"); return; } QJsonObject newest; for (const QJsonValue &releaseVal : releases) { QJsonObject release = releaseVal.toObject(); if (!release["draft"].toBool() && (Settings::instance().usePrereleases() || !release["prerelease"].toBool())) { if (newest.empty() || (VersionInfo(release["tag_name"].toString()) > VersionInfo(newest["tag_name"].toString()))) { newest = release; } } } if (!newest.empty()) { VersionInfo newestVer(newest["tag_name"].toString()); if (newestVer > this->m_MOVersion) { m_UpdateCandidate = newest; qDebug("update available: %s -> %s", qUtf8Printable(this->m_MOVersion.displayString(3)), qUtf8Printable(newestVer.displayString(3))); emit updateAvailable(); } else if (newestVer < this->m_MOVersion) { // this could happen if the user switches from using prereleases to // stable builds. Should we downgrade? qDebug("This version is newer than the latest released one: %s -> %s", qUtf8Printable(this->m_MOVersion.displayString(3)), qUtf8Printable(newestVer.displayString(3))); } } }); } //Catch all is bad by design, should be improved catch (...) { qDebug("Unable to connect to github.com to check version"); } } void SelfUpdater::startUpdate() { // the button can't be pressed if there isn't an update candidate Q_ASSERT(!m_UpdateCandidate.empty()); QMessageBox query(QMessageBox::Question, tr("New update available (%1)") .arg(m_UpdateCandidate["tag_name"].toString()), tr("Do you want to install update? All your mods and setup will be left untouched.\nSelect Show Details option to see the full change-log."), QMessageBox::Yes | QMessageBox::Cancel, m_Parent); query.setDetailedText(m_UpdateCandidate["body"].toString()); query.button(QMessageBox::Yes)->setText(tr("Install")); int res = query.exec(); if (query.result() == QMessageBox::Yes) { bool found = false; for (const QJsonValue &assetVal : m_UpdateCandidate["assets"].toArray()) { QJsonObject asset = assetVal.toObject(); if (asset["content_type"].toString() == "application/x-msdownload") { openOutputFile(asset["name"].toString()); download(asset["browser_download_url"].toString()); found = true; break; } } if (!found) { QMessageBox::warning( m_Parent, tr("Download failed"), tr("Failed to find correct download, please try again later.")); } } } void SelfUpdater::showProgress() { if (m_Progress == nullptr) { m_Progress = new QProgressDialog(m_Parent, Qt::Dialog); connect(m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel())); } m_Progress->setModal(true); m_Progress->show(); m_Progress->setValue(0); m_Progress->setWindowTitle(tr("Update")); m_Progress->setLabelText(tr("Download in progress")); } void SelfUpdater::closeProgress() { if (m_Progress != nullptr) { m_Progress->hide(); m_Progress->deleteLater(); m_Progress = nullptr; } } void SelfUpdater::openOutputFile(const QString &fileName) { QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName; qDebug("downloading to %s", qUtf8Printable(outputPath)); m_UpdateFile.setFileName(outputPath); m_UpdateFile.open(QIODevice::WriteOnly); } void SelfUpdater::download(const QString &downloadLink) { QNetworkAccessManager *accessManager = m_Interface->getAccessManager(); QUrl dlUrl(downloadLink); QNetworkRequest request(dlUrl); m_Canceled = false; m_Reply = accessManager->get(request); showProgress(); connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); connect(m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); connect(m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); } void SelfUpdater::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { if (m_Reply != nullptr) { if (m_Canceled) { m_Reply->abort(); } else { if (bytesTotal != 0) { if (m_Progress != nullptr) { m_Progress->setValue((bytesReceived * 100) / bytesTotal); } } } } } void SelfUpdater::downloadReadyRead() { if (m_Reply != nullptr) { m_UpdateFile.write(m_Reply->readAll()); } } void SelfUpdater::downloadFinished() { int error = QNetworkReply::NoError; if (m_Reply != nullptr) { if (m_Reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 302) { QUrl url = m_Reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); m_UpdateFile.reset(); download(url.toString()); return; } m_UpdateFile.write(m_Reply->readAll()); error = m_Reply->error(); if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) { m_Canceled = true; } closeProgress(); m_Reply->close(); m_Reply->deleteLater(); m_Reply = nullptr; } m_UpdateFile.close(); if ((m_UpdateFile.size() == 0) || (error != QNetworkReply::NoError) || m_Canceled) { if (!m_Canceled) { reportError(tr("Download failed: %1").arg(error)); } m_UpdateFile.remove(); return; } qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData()); try { installUpdate(); } catch (const std::exception &e) { reportError(tr("Failed to install update: %1").arg(e.what())); } } void SelfUpdater::downloadCancel() { m_Canceled = true; } void SelfUpdater::installUpdate() { const QString parameters = "/DIR=\"" + qApp->applicationDirPath() + "\" "; if (shell::Execute(m_UpdateFile.fileName(), parameters)) { QCoreApplication::quit(); } else { reportError(tr("Failed to start %1").arg(m_UpdateFile.fileName())); } m_UpdateFile.remove(); } void SelfUpdater::report7ZipError(QString const &errorMessage) { QMessageBox::critical(m_Parent, tr("Error"), errorMessage); }