From 981f8b3acf7e76f27c02f4ced80d55b424cc7497 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 3 Feb 2013 12:49:25 +0100 Subject: initial commit to mercurial repository. Corresponds to MO version 0.12.6 --- src/selfupdater.cpp | 455 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 455 insertions(+) create mode 100644 src/selfupdater.cpp (limited to 'src/selfupdater.cpp') diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp new file mode 100644 index 00000000..884f9647 --- /dev/null +++ b/src/selfupdater.cpp @@ -0,0 +1,455 @@ +/* +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 "utility.h" +#include "installationmanager.h" +#include "report.h" +#include "messagedialog.h" +#include "downloadmanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +typedef Archive* (*CreateArchiveType)(); + + +template T resolveFunction(QLibrary &lib, const char *name) +{ + T temp = reinterpret_cast(lib.resolve(name)); + if (temp == NULL) { + throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); + } + return temp; +} + + +SelfUpdater::SelfUpdater(NexusInterface *nexusInterface, QWidget *parent) + : QObject(parent), m_Parent(parent), m_Interface(nexusInterface), m_UpdateRequestID(-1), + m_Reply(NULL), m_Progress(parent), m_Attempts(3) +{ + m_Progress.setMaximum(100); + + QLibrary archiveLib("dlls\\archive.dll"); + if (!archiveLib.load()) { + throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); + } + + CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive"); + + m_CurrentArchive = CreateArchiveFunc(); + if (!m_CurrentArchive->isValid()) { + throw MyException(InstallationManager::getErrorString(m_CurrentArchive->getLastError())); + } + + connect(&m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel())); + + VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); + + m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16); +} + + +SelfUpdater::~SelfUpdater() +{ + delete m_CurrentArchive; +} + + +void SelfUpdater::testForUpdate() +{ +/* if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { + emit updateAvailable(); + return; + }*/ + + if (m_UpdateRequestID == -1) { + m_UpdateRequestID = m_Interface->requestDescription( + SkyrimInfo::getNexusModIDStatic(), this, QVariant(), + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } +} + +void SelfUpdater::startUpdate() +{ +/* if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { + m_UpdateFile.setFileName(QCoreApplication::applicationDirPath() + "/mo_test_update.7z"); + installUpdate(); + return; + }*/ + + if ((m_UpdateRequestID == -1) && + (!m_NewestVersion.isEmpty())) { + if (QMessageBox::question(m_Parent, tr("Update"), + tr("An update is available (newest version: %1), do you want to install it?").arg(m_NewestVersion), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_UpdateRequestID = m_Interface->requestFiles(SkyrimInfo::getNexusModIDStatic(), + this, m_NewestVersion, + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } + } +} + + +void SelfUpdater::download(const QString &downloadLink, const QString &fileName) +{ + qDebug("download: %s", downloadLink.toUtf8().constData()); + QNetworkAccessManager *accessManager = m_Interface->getAccessManager(); + QUrl dlUrl(downloadLink); + QNetworkRequest request(dlUrl); + m_Canceled = false; + m_Reply = accessManager->get(request); + m_UpdateFile.setFileName(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory()).append("\\").append(fileName))); + m_UpdateFile.open(QIODevice::WriteOnly); + m_Progress.setModal(true); + m_Progress.show(); + m_Progress.setValue(0); + m_Progress.setWindowTitle(tr("Update")); + m_Progress.setLabelText(tr("Download in progress")); + + 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 != NULL) { + if (m_Canceled) { + m_Reply->abort(); + } else { + if (bytesTotal != 0) { + m_Progress.setValue((bytesReceived * 100) / bytesTotal); + } + } + } +} + + +void SelfUpdater::downloadReadyRead() +{ + if (m_Reply != NULL) { + m_UpdateFile.write(m_Reply->readAll()); + } +} + + +void SelfUpdater::downloadFinished() +{ + int error = QNetworkReply::NoError; + + if (m_Reply != NULL) { + m_UpdateFile.write(m_Reply->readAll()); + + error = m_Reply->error(); + + if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) { + m_Canceled = true; + } + + m_Progress.hide(); + m_Reply->close(); + m_Reply->deleteLater(); + m_Reply = NULL; + } + + 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 mopath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())); + + QString backupPath = mopath.mid(0).append("/update_backup"); + QDir().mkdir(backupPath); + + // rename files that are currently open so we can unpack the update + if (!m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(m_UpdateFile.fileName())).c_str(), + new MethodCallback(this, &SelfUpdater::queryPassword))) { + throw MyException(tr("failed to open archive \"%1\": %2") + .arg(m_UpdateFile.fileName()) + .arg(InstallationManager::getErrorString(m_CurrentArchive->getLastError()))); + } + + // move all files contained in the archive out of the way, + // otherwise we can't overwrite everything + FileData* const *data; + size_t size; + m_CurrentArchive->getFileList(data, size); + + for (size_t i = 0; i < size; ++i) { + QString outputName = ToQString(data[i]->getFileName()); + if (outputName.startsWith("ModOrganizer\\", Qt::CaseInsensitive)) { + outputName = outputName.mid(13); + data[i]->setOutputFileName(ToWString(outputName).c_str()); + } else if (outputName == "ModOrganizer") { + data[i]->setSkip(true); + } + QFile file(mopath.mid(0).append("/").append(outputName)); + if (file.exists()) { + file.rename(backupPath.mid(0).append("/").append(outputName)); + } + } + + // now unpack the archive into the mo directory + if (!m_CurrentArchive->extract(GameInfo::instance().getOrganizerDirectory().c_str(), + new MethodCallback(this, &SelfUpdater::updateProgress), + new MethodCallback(this, &SelfUpdater::updateProgressFile), + new MethodCallback(this, &SelfUpdater::report7ZipError))) { + throw std::runtime_error("extracting failed"); + } + + m_CurrentArchive->close(); + + m_UpdateFile.remove(); + + QMessageBox::information(m_Parent, tr("Update"), tr("Update installed, Mod Organizer will now be restarted.")); + + QProcess newProcess; + if (QFile::exists(mopath.mid(0).append("/ModOrganizer.exe"))) { + newProcess.startDetached(mopath.mid(0).append("/ModOrganizer.exe"), QStringList("update")); + } else { + newProcess.startDetached(mopath.mid(0).append("/ModOrganiser.exe"), QStringList("update")); + } + emit restart(); +} + +void SelfUpdater::queryPassword(LPSTR) +{ + // nop +} + +void SelfUpdater::updateProgress(float) +{ + // nop +} + +void SelfUpdater::updateProgressFile(LPCWSTR) +{ + // nop +} + +void SelfUpdater::report7ZipError(LPCWSTR errorMessage) +{ + QMessageBox::critical(m_Parent, tr("Error"), ToQString(errorMessage)); +} + + +QString SelfUpdater::retrieveNews(const QString &description) +{ + QStringList temp = description.split("[s][/s]"); + if (temp.length() < 2) { + return QString(); + } else { + return temp.at(1); + } +} + + +void SelfUpdater::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID) +{ + if (requestID == m_UpdateRequestID) { + m_UpdateRequestID = -1; + + QVariantMap result = resultData.toMap(); + QString motd = retrieveNews(result["description"].toString()).trimmed(); + if (motd.length() != 0) { + emit motdAvailable(motd); + } + + m_NewestVersion = result["version"].toString(); + if (m_NewestVersion.isEmpty()) { + QTimer::singleShot(5000, this, SLOT(testForUpdate())); + } + + VersionInfo currentVersion(m_MOVersion); + VersionInfo newestVersion(m_NewestVersion); + + if (!m_NewestVersion.isEmpty() && (currentVersion < newestVersion)) { + emit updateAvailable(); + } else if (newestVersion < currentVersion) { + qDebug("this version is newer than the current version on nexus (%s vs %s)", + currentVersion.canonicalString().toUtf8().constData(), + newestVersion.canonicalString().toUtf8().constData()); + } + } +} + + +void SelfUpdater::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) +{ + if (requestID != m_UpdateRequestID) { + return; + } + QString version = userData.toString(); + + m_UpdateRequestID = -1; + + if (!resultData.canConvert()) { + qCritical("invalid files result: %s", resultData.toString().toUtf8().constData()); + reportError(tr("Failed to parse response. Please report this as a bug and include the file mo_interface.log.")); + return; + } + + QVariantList result = resultData.toList(); + + QRegExp updateExpList(QString("updates version ([0-9., ]*) to %1").arg(version)); + QRegExp updateExpRange(QString("updates version ([0-9.]*) - ([0-9.]*) to %1").arg(version)); + int updateFileID = -1; + QString updateFileName; + int mainFileID = -1; + QString mainFileName; + int mainFileSize = 0; + + foreach(QVariant file, result) { + QVariantMap fileInfo = file.toMap(); + if (!fileInfo["uri"].toString().endsWith(".7z")) { + continue; + } + + if (fileInfo["version"].toString() == version) { + if (fileInfo["category_id"].toInt() == 2) { + QString description = fileInfo["description"].toString(); + // update + if (updateExpList.indexIn(description) != -1) { + // there is an update for the newest version of MO, but does + // it apply to the current version? + QStringList supportedVersions = updateExpList.cap(1).split(QRegExp(",[ ]*"), QString::SkipEmptyParts); + if (supportedVersions.contains(m_MOVersion.canonicalString())) { + updateFileID = fileInfo["id"].toInt(); + updateFileName = fileInfo["uri"].toString(); + } else { + qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); + } + } else if (updateExpRange.indexIn(description) != -1) { + VersionInfo rangeLowEnd(updateExpRange.cap(1)); + VersionInfo rangeHighEnd(updateExpRange.cap(2)); + if ((rangeLowEnd <= m_MOVersion) && + (m_MOVersion <= rangeHighEnd)) { + updateFileID = fileInfo["id"].toInt(); + updateFileName = fileInfo["uri"].toString(); + break; + } else { + qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); + } + } else { + qWarning("invalid update description: %s", + description.toUtf8().constData()); + } + } else if (fileInfo["category_id"].toInt() == 1) { + mainFileID = fileInfo["id"].toInt(); + mainFileName = fileInfo["uri"].toString(); + mainFileSize = fileInfo["size"].toInt(); + } + } + } + + if (updateFileID != -1) { + qDebug("update available: %d", updateFileID); + m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), + updateFileID, this, updateFileName, + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } else if (mainFileID != -1) { + qDebug("full download required: %d", mainFileID); + if (QMessageBox::question(m_Parent, tr("Update"), + tr("No incremental update available for this version, " + "the complete package needs to be downloaded (%1 kB)").arg(mainFileSize), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), + mainFileID, this, mainFileName, + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } + } else { + qCritical("no file for update found"); + MessageDialog::showMessage(tr("no file for update found. Please update manually."), m_Parent); + m_Progress.hide(); + } +} + + +void SelfUpdater::nxmRequestFailed(int, QVariant, int requestID, const QString &errorMessage) +{ + if (requestID == m_UpdateRequestID) { + m_UpdateRequestID = -1; + if (m_Attempts > 0) { + QTimer::singleShot(60000, this, SLOT(testForUpdate())); + --m_Attempts; + } else { + MessageDialog::showMessage(tr("Failed to retrieve update information: %1").arg(errorMessage), m_Parent); + } + } +} + + +void SelfUpdater::nxmDownloadURLsAvailable(int, int, QVariant userData, QVariant resultData, int requestID) +{ + if (requestID == m_UpdateRequestID) { + m_UpdateRequestID = -1; + QVariantList serverList = resultData.toList(); + if (serverList.count() != 0) { + qSort(serverList.begin(), serverList.end(), DownloadManager::ServerByPreference); + + QVariantMap dlServer = serverList.first().toMap(); + + download(dlServer["URI"].toString(), userData.toString()); + } else { + MessageDialog::showMessage(tr("No download server available. Please try again later."), m_Parent); + m_Progress.hide(); + } + } +} + -- cgit v1.3.1