diff options
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp')
| -rw-r--r-- | SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp | 1034 |
1 files changed, 62 insertions, 972 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp index 3663a1b..87df73b 100644 --- a/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp @@ -1,461 +1,100 @@ #ifdef PORTABLE_CONFIG #include "updatemanager.h" -#include "services/pluginmanager.h" #include "services/notifymanager.h" #include "common/unused.h" -#include <QTemporaryDir> -#include <QNetworkAccessManager> -#include <QNetworkReply> -#include <QNetworkRequest> -#include <QUrl> -#include <QUrlQuery> #include <QDebug> +#include <QRegularExpression> #include <QCoreApplication> -#include <QJsonDocument> -#include <QJsonObject> -#include <QJsonArray> -#include <QJsonValue> -#include <QProcess> -#include <QThread> -#include <QtConcurrent/QtConcurrent> - -#ifdef Q_OS_WIN32 -#include "JlCompress.h" -#include <windows.h> -#include <shellapi.h> -#endif - -// Note on creating update packages: -// Packages for Linux and MacOSX should be an archive of _contents_ of SQLiteStudio directory, -// while for Windows it should be an archive of SQLiteStudio directory itself. - -QString UpdateManager::staticErrorMessage; -UpdateManager::RetryFunction UpdateManager::retryFunction = nullptr; +#include <QFileInfo> +#include <QtConcurrent/QtConcurrentRun> UpdateManager::UpdateManager(QObject *parent) : QObject(parent) { - networkManager = new QNetworkAccessManager(this); - connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(finished(QNetworkReply*))); - connect(this, SIGNAL(updatingError(QString)), NOTIFY_MANAGER, SLOT(error(QString))); -} - -UpdateManager::~UpdateManager() -{ - cleanup(); -} + qRegisterMetaType<QList<UpdateManager::UpdateEntry>>(); -void UpdateManager::checkForUpdates(bool force) -{ - getUpdatesMetadata(updatesCheckReply, force); -} - -void UpdateManager::update() -{ - if (updatesGetUrlsReply || updatesInProgress) - return; - - getUpdatesMetadata(updatesGetUrlsReply); -} + connect(this, SIGNAL(updatingError(QString)), NOTIFY_MANAGER, SLOT(error(QString))); -QString UpdateManager::getPlatformForUpdate() const -{ -#if defined(Q_OS_LINUX) - if (QSysInfo::WordSize == 64) - return "linux64"; - else - return "linux32"; -#elif defined(Q_OS_WIN) - return "win32"; + QString updateBinary = +#if defined(Q_OS_WIN) + "UpdateSQLiteStudio.exe"; +#elif defined(Q_OS_LINUX) + "UpdateSQLiteStudio"; #elif defined(Q_OS_OSX) - return "macosx"; + "../../UpdateSQLiteStudio.app/Contents/MacOS/UpdateSQLiteStudio"; #else - return QString(); + ""; #endif -} - -QString UpdateManager::getCurrentVersions() const -{ - QJsonArray versionsArray; - - QJsonObject arrayEntry; - arrayEntry["component"] = "SQLiteStudio"; - arrayEntry["version"] = SQLITESTUDIO->getVersionString(); - versionsArray.append(arrayEntry); - - for (const PluginManager::PluginDetails& details : PLUGINS->getAllPluginDetails()) - { - if (details.builtIn) - continue; - arrayEntry["component"] = details.name; - arrayEntry["version"] = details.versionString; - versionsArray.append(arrayEntry); + if (!updateBinary.isEmpty()) { + updateBinaryAbsolutePath = QFileInfo(QCoreApplication::applicationDirPath() + "/" + updateBinary).absoluteFilePath(); } - - QJsonObject topObj; - topObj["versions"] = versionsArray; - - QJsonDocument doc(topObj); - return QString::fromLatin1(doc.toJson(QJsonDocument::Compact)); -} - -bool UpdateManager::isPlatformEligibleForUpdate() const -{ - return !getPlatformForUpdate().isNull() && getDistributionType() != DistributionType::OS_MANAGED; -} - -#if defined(Q_OS_WIN32) -bool UpdateManager::executePreFinalStepWin(const QString &tempDir, const QString &backupDir, const QString &appDir, bool reqAdmin) -{ - bool res; - if (reqAdmin) - res = executeFinalStepAsRootWin(tempDir, backupDir, appDir); - else - res = executeFinalStep(tempDir, backupDir, appDir); - - if (res) - { - QFileInfo path(qApp->applicationFilePath()); - QProcess::startDetached(appDir + "/" + path.fileName(), {WIN_POST_FINAL_UPDATE_OPTION_NAME, tempDir}); - } - return res; } -#endif -void UpdateManager::handleAvailableUpdatesReply(QNetworkReply* reply) -{ - if (reply->error() != QNetworkReply::NoError) - { - updatingFailed(tr("An error occurred while checking for updates: %1.").arg(reply->errorString())); - reply->deleteLater(); - return; - } - - QJsonParseError err; - QByteArray data = reply->readAll(); - reply->deleteLater(); - - QJsonDocument doc = QJsonDocument::fromJson(data, &err); - if (err.error != QJsonParseError::NoError) - { - qWarning() << "Invalid response from update service:" << err.errorString() << "\n" << "The data was:" << QString::fromLatin1(data); - notifyWarn(tr("Could not check available updates, because server responded with invalid message format. It is safe to ignore this warning.")); - return; - } - - QList<UpdateEntry> updates = readMetadata(doc); - if (updates.size() > 0) - emit updatesAvailable(updates); - else - emit noUpdatesAvailable(); -} - -void UpdateManager::getUpdatesMetadata(QNetworkReply*& replyStoragePointer, bool force) +UpdateManager::~UpdateManager() { -#ifdef PORTABLE_CONFIG - if ((!CFG_CORE.General.CheckUpdatesOnStartup.get() && !force) || !isPlatformEligibleForUpdate() || replyStoragePointer) - return; - - QUrlQuery query; - query.addQueryItem("platform", getPlatformForUpdate()); - query.addQueryItem("data", getCurrentVersions()); - QUrl url(QString::fromLatin1(updateServiceUrl) + "?" + query.query(QUrl::FullyEncoded)); - QNetworkRequest request(url); - replyStoragePointer = networkManager->get(request); -#endif } -void UpdateManager::handleUpdatesMetadata(QNetworkReply* reply) +void UpdateManager::checkForUpdates() { - if (reply->error() != QNetworkReply::NoError) - { - updatingFailed(tr("An error occurred while reading updates metadata: %1.").arg(reply->errorString())); - reply->deleteLater(); + if (!CFG_CORE.General.CheckUpdatesOnStartup.get()) return; - } - QJsonParseError err; - QByteArray data = reply->readAll(); - reply->deleteLater(); - - QJsonDocument doc = QJsonDocument::fromJson(data, &err); - if (err.error != QJsonParseError::NoError) - { - qWarning() << "Invalid response from update service for getting metadata:" << err.errorString() << "\n" << "The data was:" << QString::fromLatin1(data); - notifyWarn(tr("Could not download updates, because server responded with invalid message format. " - "You can try again later or download and install updates manually. See <a href=\"%1\">User Manual</a> for details.").arg(manualUpdatesHelpUrl)); + if (updateBinaryAbsolutePath.isEmpty()) { + qDebug() << "Updater binary not defined. Skipping updates checking."; return; } - tempDir = new QTemporaryDir(); - if (!tempDir->isValid()) { - notifyWarn(tr("Could not create temporary directory for downloading the update. Updating aborted.")); + if (!QFileInfo(updateBinaryAbsolutePath).exists()) { + QString errorDetails = tr("Updates installer executable is missing."); + emit updatingError(tr("Unable to check for updates (%1)").arg(errorDetails.trimmed())); + qWarning() << "Error while checking for updates: " << errorDetails; return; } - updatesInProgress = true; - updatesToDownload = readMetadata(doc); - totalDownloadsCount = updatesToDownload.size(); - totalPercent = 0; - - if (totalDownloadsCount == 0) - { - updatingFailed(tr("There was no updates to download. Updating aborted.")); - return; - } - - downloadUpdates(); + QtConcurrent::run(this, &UpdateManager::checkForUpdatesAsync); } -QList<UpdateManager::UpdateEntry> UpdateManager::readMetadata(const QJsonDocument& doc) +void UpdateManager::checkForUpdatesAsync() { - QList<UpdateEntry> updates; - UpdateEntry entry; - QJsonObject obj = doc.object(); - QJsonArray versionsArray = obj["newVersions"].toArray(); - QJsonObject entryObj; - for (const QJsonValue& value : versionsArray) - { - entryObj = value.toObject(); - entry.compontent = entryObj["component"].toString(); - entry.version = entryObj["version"].toString(); - entry.url = entryObj["url"].toString(); - updates << entry; - } - - return updates; -} - -void UpdateManager::downloadUpdates() -{ - if (updatesToDownload.size() == 0) + QProcess proc; + proc.start(updateBinaryAbsolutePath, {"--checkupdates"}); + if (!waitForProcess(proc)) { - QtConcurrent::run(this, &UpdateManager::installUpdates); - return; - } + QString errorDetails = QString::fromLocal8Bit(proc.readAllStandardError()); - UpdateEntry entry = updatesToDownload.takeFirst(); - currentJobTitle = tr("Downloading: %1").arg(entry.compontent); - emit updatingProgress(currentJobTitle, 0, totalPercent); + if (errorDetails.toLower().contains("no updates")) { + emit noUpdatesAvailable(); + return; + } - QStringList parts = entry.url.split("/"); - if (parts.size() < 1) - { - updatingFailed(tr("Could not determinate file name from update URL: %1. Updating aborted.").arg(entry.url)); - return; - } + if (errorDetails.isEmpty()) + errorDetails = tr("details are unknown"); - QString path = tempDir->path() + QLatin1Char('/') + parts.last(); - currentDownloadFile = new QFile(path); - if (!currentDownloadFile->open(QIODevice::WriteOnly)) - { - updatingFailed(tr("Failed to open file '%1' for writting: %2. Updating aborted.").arg(path, currentDownloadFile->errorString())); + emit updatingError(tr("Unable to check for updates (%1)").arg(errorDetails.trimmed())); + qWarning() << "Error while checking for updates: " << errorDetails; return; } - updatesToInstall[entry.compontent] = path; - - QNetworkRequest request(QUrl(entry.url)); - updatesGetReply = networkManager->get(request); - connect(updatesGetReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(downloadProgress(qint64,qint64))); - connect(updatesGetReply, SIGNAL(readyRead()), this, SLOT(readDownload())); + processCheckResults(proc.readAllStandardOutput()); } -void UpdateManager::updatingFailed(const QString& errMsg) -{ - cleanup(); - updatesInProgress = false; - emit updatingError(errMsg); -} - -void UpdateManager::installUpdates() +void UpdateManager::update() { - currentJobTitle = tr("Installing updates."); - totalPercent = (totalDownloadsCount - updatesToDownload.size()) * 100 / (totalDownloadsCount + 1); - emit updatingProgress(currentJobTitle, 0, totalPercent); - - requireAdmin = doRequireAdminPrivileges(); - - QTemporaryDir installTempDir; - QString appDirName = QDir(getAppDirPath()).dirName(); - QString targetDir = installTempDir.path() + QLatin1Char('/') + appDirName; - if (!copyRecursively(getAppDirPath(), targetDir)) + bool success = QProcess::startDetached(updateBinaryAbsolutePath, {"--updater"}); + if (!success) { - updatingFailed(tr("Could not copy current application directory into %1 directory.").arg(installTempDir.path())); + emit updatingError(tr("Unable to run updater application (%1). Please report this.").arg(updateBinaryAbsolutePath)); return; } - emit updatingProgress(currentJobTitle, 40, totalPercent); - - int i = 0; - int updatesCnt = updatesToInstall.size(); - for (const QString& component : updatesToInstall.keys()) - { - if (!installComponent(component, targetDir)) - { - cleanup(); - updatesInProgress = false; - return; - } - i++; - emit updatingProgress(currentJobTitle, (30 + (50 / updatesCnt * i)), totalPercent); - } - - if (!executeFinalStep(targetDir)) - { - cleanup(); - updatesInProgress = false; - return; - } - - currentJobTitle = QString(); - totalPercent = 100; - emit updatingProgress(currentJobTitle, 100, totalPercent); - cleanup(); - updatesInProgress = false; -#ifdef Q_OS_WIN32 - installTempDir.setAutoRemove(false); -#endif - - SQLITESTUDIO->setImmediateQuit(true); qApp->exit(0); } -bool UpdateManager::executeFinalStep(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ - bool isWin = false; -#ifdef Q_OS_WIN32 - isWin = true; - - // Windows needs to wait for previus process to exit - QThread::sleep(3); - - QDir dir(backupDir); - QString dirName = dir.dirName(); - dir.cdUp(); - if (!dir.mkdir(dirName)) - { - staticUpdatingFailed(tr("Could not create directory %1.").arg(backupDir)); - return false; - } -#endif - while (!moveDir(appDir, backupDir, isWin)) - { - if (!retryFunction) - { - staticUpdatingFailed(tr("Could not rename directory %1 to %2.\nDetails: %3").arg(appDir, backupDir, staticErrorMessage)); - return false; - } - - if (!retryFunction(tr("Cannot not rename directory %1 to %2.\nDetails: %3").arg(appDir, backupDir, staticErrorMessage))) - return false; - } - - if (!moveDir(tempDir, appDir, isWin)) - { - if (!moveDir(backupDir, appDir, isWin)) - { - staticUpdatingFailed(tr("Could not move directory %1 to %2 and also failed to restore original directory, " - "so the original SQLiteStudio directory is now located at: %3").arg(tempDir, appDir, backupDir)); - } - else - { - staticUpdatingFailed(tr("Could not rename directory %1 to %2. Rolled back to the original SQLiteStudio version.").arg(tempDir, appDir)); - } - deleteDir(backupDir); - return false; - } - - deleteDir(backupDir); - return true; -} - -bool UpdateManager::handleUpdateOptions(const QStringList& argList, int& returnCode) -{ - if (argList.size() == 5 && argList[1] == UPDATE_OPTION_NAME) - { - bool result = UpdateManager::executeFinalStep(argList[2], argList[3], argList[4]); - if (result) - returnCode = 0; - else - returnCode = 1; - - return true; - } - -#ifdef Q_OS_WIN32 - if (argList.size() == 6 && argList[1] == WIN_PRE_FINAL_UPDATE_OPTION_NAME) - { - bool result = UpdateManager::executePreFinalStepWin(argList[2], argList[3], argList[4], (bool)argList[5].toInt()); - if (result) - returnCode = 0; - else - returnCode = -1; - - return true; - } - - if (argList.size() == 3 && argList[1] == WIN_POST_FINAL_UPDATE_OPTION_NAME) - { - QThread::sleep(1); // to make sure that the previous process has quit - returnCode = 0; - UpdateManager::executePostFinalStepWin(argList[2]); - return true; - } -#endif - - return false; -} - -QString UpdateManager::getStaticErrorMessage() -{ - return staticErrorMessage; -} - -bool UpdateManager::executeFinalStep(const QString& tempDir) -{ - QString appDir = getAppDirPath(); - - // Find inexisting dir name next to app dir - QDir backupDir(getBackupDir(appDir)); - -#if defined(Q_OS_WIN32) - return runAnotherInstanceForUpdate(tempDir, backupDir.absolutePath(), qApp->applicationDirPath(), requireAdmin); -#else - bool res; - if (requireAdmin) - res = executeFinalStepAsRoot(tempDir, backupDir.absolutePath(), appDir); - else - res = executeFinalStep(tempDir, backupDir.absolutePath(), appDir); - - if (res) - QProcess::startDetached(qApp->applicationFilePath(), QStringList()); - - return res; -#endif -} - -bool UpdateManager::installComponent(const QString& component, const QString& tempDir) -{ - if (!unpackToDir(updatesToInstall[component], tempDir)) - { - updatingFailed(tr("Could not unpack component %1 into %2 directory.").arg(component, tempDir)); - return false; - } - - // In future here we might also delete/change some files, according to some update script. - return true; -} - -void UpdateManager::cleanup() +bool UpdateManager::isPlatformEligibleForUpdate() const { - safe_delete(currentDownloadFile); - safe_delete(tempDir); - updatesToDownload.clear(); - updatesToInstall.clear(); - requireAdmin = false; + return getDistributionType() != DistributionType::OS_MANAGED; } bool UpdateManager::waitForProcess(QProcess& proc) @@ -481,582 +120,33 @@ bool UpdateManager::waitForProcess(QProcess& proc) return true; } -QString UpdateManager::readError(QProcess& proc, bool reverseOrder) -{ - QString err = QString::fromLocal8Bit(reverseOrder ? proc.readAllStandardOutput() : proc.readAllStandardError()); - if (err.isEmpty()) - err = QString::fromLocal8Bit(reverseOrder ? proc.readAllStandardError() : proc.readAllStandardOutput()); - - QString errStr = proc.errorString(); - if (!errStr.isEmpty()) - err += "\n" + errStr; - - return err; -} - -void UpdateManager::staticUpdatingFailed(const QString& errMsg) +void UpdateManager::processCheckResults(const QByteArray &results) { -#if defined(Q_OS_WIN32) - staticErrorMessage = errMsg; -#else - UPDATES->handleStaticFail(errMsg); -#endif - qCritical() << errMsg; -} - -bool UpdateManager::executeFinalStepAsRoot(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ -#if defined(Q_OS_LINUX) - return executeFinalStepAsRootLinux(tempDir, backupDir, appDir); -#elif defined(Q_OS_WIN32) - return executeFinalStepAsRootWin(tempDir, backupDir, appDir); -#elif defined(Q_OS_MACX) - return executeFinalStepAsRootMac(tempDir, backupDir, appDir); -#else - qCritical() << "Unknown update platform in UpdateManager::executeFinalStepAsRoot() for package" << packagePath; - return false; -#endif -} - -#if defined(Q_OS_LINUX) -bool UpdateManager::executeFinalStepAsRootLinux(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ - QStringList args = {qApp->applicationFilePath(), UPDATE_OPTION_NAME, tempDir, backupDir, appDir}; - - QProcess proc; - LinuxPermElevator elevator = findPermElevatorForLinux(); - switch (elevator) - { - case LinuxPermElevator::KDESU: - proc.setProgram("kdesu"); - args.prepend("-t"); - proc.setArguments(args); - break; - case LinuxPermElevator::GKSU: - proc.setProgram("gksu"); // TODO test gksu updates - proc.setArguments(args); - break; - case LinuxPermElevator::PKEXEC: - { - // We call CLI for doing final step, because pkexec runs cmd completly in root env, so there's no X server. - args[0] += "cli"; - - QStringList newArgs; - for (const QString& arg : args) - newArgs << wrapCmdLineArgument(arg); - - QString cmd = "cd " + wrapCmdLineArgument(qApp->applicationDirPath()) +"; " + newArgs.join(" "); - - proc.setProgram("pkexec"); - proc.setArguments({"sh", "-c", cmd}); - } - break; - case LinuxPermElevator::NONE: - updatingFailed(tr("Could not find permissions elevator application to run update as a root. Looked for: %1").arg("kdesu, gksu, pkexec")); - return false; - } - - proc.start(); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Could not execute final updating steps as root: %1").arg(readError(proc, (elevator == LinuxPermElevator::KDESU)))); - return false; - } - - return true; -} -#endif - -#ifdef Q_OS_MACX -bool UpdateManager::executeFinalStepAsRootMac(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ - // Prepare script for updater - // osascript -e "do shell script \"stufftorunasroot\" with administrator privileges" - QStringList args = {wrapCmdLineArgument(qApp->applicationFilePath() + "cli"), - UPDATE_OPTION_NAME, - wrapCmdLineArgument(tempDir), - wrapCmdLineArgument(backupDir), - wrapCmdLineArgument(appDir)}; - QProcess proc; - - QString innerCmd = wrapCmdLineArgument(args.join(" ")); - - static_qstring(scriptTpl, "do shell script %1 with administrator privileges"); - QString scriptCmd = scriptTpl.arg(innerCmd); - - // Prepare updater temporary directory - QTemporaryDir updaterDir; - if (!updaterDir.isValid()) - { - updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(tr("Cannot create temporary directory for updater."))); - return false; - } - - // Create updater script - QString scriptPath = updaterDir.path() + "/UpdateSQLiteStudio.scpt"; - QFile updaterScript(scriptPath); - if (!updaterScript.open(QIODevice::WriteOnly)) - { - updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(tr("Cannot create updater script file."))); - return false; - } - updaterScript.write(scriptCmd.toLocal8Bit()); - updaterScript.close(); - - // Compile script to updater application - QString updaterApp = updaterDir.path() + "/UpdateSQLiteStudio.app"; - proc.setProgram("osacompile"); - proc.setArguments({"-o", updaterApp, scriptPath}); - proc.start(); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(readError(proc))); - return false; - } - - // Execute updater - proc.setProgram(updaterApp + "/Contents/MacOS/applet"); - proc.setArguments({}); - proc.start(); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Could not execute final updating steps as admin: %1").arg(readError(proc))); - return false; - } - - // Validating update - // The updater script will not return error if the user canceled the password prompt. - // We need to check if the update was actually made and return true only then. - if (QDir(tempDir).exists()) - { - // Temp dir still exists, so it was not moved by root process - updatingFailed(tr("Updating canceled.")); - return false; - } - - return true; -} -#endif - -#ifdef Q_OS_WIN32 -bool UpdateManager::executeFinalStepAsRootWin(const QString& tempDir, const QString& backupDir, const QString& appDir) -{ - QString updateBin = qApp->applicationDirPath() + "/" + WIN_UPDATER_BINARY; - - QString installFilePath = tempDir + "/" + WIN_INSTALL_FILE; - QFile installFile(installFilePath); - installFile.open(QIODevice::WriteOnly); - QString nl("\n"); - installFile.write(UPDATE_OPTION_NAME); - installFile.write(nl.toLocal8Bit()); - installFile.write(backupDir.toLocal8Bit()); - installFile.write(nl.toLocal8Bit()); - installFile.write(appDir.toLocal8Bit()); - installFile.write(nl.toLocal8Bit()); - installFile.close(); - - int res = (int)::ShellExecuteA(0, "runas", updateBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL); - if (res < 32) - { - staticUpdatingFailed(tr("Could not execute final updating steps as administrator.")); - return false; - } - - // Since I suck as a developer and I cannot implement a simple synchronous app call under Windows - // (QProcess does it somehow, but I'm too lazy to look it up and probably the solution wouldn't be compatible - // with our "privileges elevation" trick above... so after all I think we're stuck with this solution for now), - // I do the workaround here, which makes this process wait for the other process to create the "done" - // file when it's done, so this process knows when the other has ended. This way we can proceed with this - // process and we will delete some directories later on, which were required by that other process. - if (!waitForFileToDisappear(installFilePath, 10)) - { - staticUpdatingFailed(tr("Could not execute final updating steps as administrator. Updater startup timed out.")); - return false; - } - - if (!waitForFileToAppear(appDir + QLatin1Char('/') + WIN_UPDATE_DONE_FILE, 30)) - { - staticUpdatingFailed(tr("Could not execute final updating steps as administrator. Updater operation timed out.")); - return false; - } - - return true; -} -#endif - -#if defined(Q_OS_WIN32) -bool UpdateManager::executePostFinalStepWin(const QString &tempDir) -{ - QString doneFile = qApp->applicationDirPath() + QLatin1Char('/') + WIN_UPDATE_DONE_FILE; - QFile::remove(doneFile); - - QDir dir(tempDir); - dir.cdUp(); - if (!deleteDir(dir.absolutePath())) - staticUpdatingFailed(tr("Could not clean up temporary directory %1. You can delete it manually at any time.").arg(dir.absolutePath())); - - QProcess::startDetached(qApp->applicationFilePath(), QStringList()); - return true; -} - -bool UpdateManager::waitForFileToDisappear(const QString &filePath, int seconds) -{ - QFile file(filePath); - while (file.exists() && seconds > 0) - { - QThread::sleep(1); - seconds--; - } - - return !file.exists(); -} - -bool UpdateManager::waitForFileToAppear(const QString &filePath, int seconds) -{ - QFile file(filePath); - while (!file.exists() && seconds > 0) - { - QThread::sleep(1); - seconds--; - } - - return file.exists(); -} - -bool UpdateManager::runAnotherInstanceForUpdate(const QString &tempDir, const QString &backupDir, const QString &appDir, bool reqAdmin) -{ - bool res = QProcess::startDetached(tempDir + "/SQLiteStudio.exe", {WIN_PRE_FINAL_UPDATE_OPTION_NAME, tempDir, backupDir, appDir, - QString::number((int)reqAdmin)}); - if (!res) - { - updatingFailed(tr("Could not run new version for continuing update.")); - return false; - } - - return true; -} -#endif - -UpdateManager::LinuxPermElevator UpdateManager::findPermElevatorForLinux() -{ -#if defined(Q_OS_LINUX) - QProcess proc; - proc.setProgram("which"); - - if (!SQLITESTUDIO->getEnv("DISPLAY").isEmpty()) - { - proc.setArguments({"kdesu"}); - proc.start(); - if (waitForProcess(proc)) - return LinuxPermElevator::KDESU; - - proc.setArguments({"gksu"}); - proc.start(); - if (waitForProcess(proc)) - return LinuxPermElevator::GKSU; - } - - proc.setArguments({"pkexec"}); - proc.start(); - if (waitForProcess(proc)) - return LinuxPermElevator::PKEXEC; -#endif - - return LinuxPermElevator::NONE; -} - -QString UpdateManager::wrapCmdLineArgument(const QString& arg) -{ - return "\"" + escapeCmdLineArgument(arg) + "\""; -} - -QString UpdateManager::escapeCmdLineArgument(const QString& arg) -{ - if (!arg.contains("\\") && !arg.contains("\"")) - return arg; - - QString str = arg; - return str.replace("\\", "\\\\").replace("\"", "\\\""); -} - -QString UpdateManager::getBackupDir(const QString &appDir) -{ - static_qstring(bakDirTpl, "%1.old%2"); - QDir backupDir(bakDirTpl.arg(appDir, "")); - int cnt = 1; - while (backupDir.exists()) - backupDir = QDir(bakDirTpl.arg(appDir, QString::number(cnt))); - - return backupDir.absolutePath(); -} - -bool UpdateManager::unpackToDir(const QString& packagePath, const QString& outputDir) -{ -#if defined(Q_OS_LINUX) - return unpackToDirLinux(packagePath, outputDir); -#elif defined(Q_OS_WIN32) - return unpackToDirWin(packagePath, outputDir); -#elif defined(Q_OS_MACX) - return unpackToDirMac(packagePath, outputDir); -#else - qCritical() << "Unknown update platform in UpdateManager::unpackToDir() for package" << packagePath; - return false; -#endif -} - -#if defined(Q_OS_LINUX) -bool UpdateManager::unpackToDirLinux(const QString &packagePath, const QString &outputDir) -{ - QProcess proc; - proc.setWorkingDirectory(outputDir); - proc.setStandardOutputFile(QProcess::nullDevice()); - proc.setStandardErrorFile(QProcess::nullDevice()); - - if (!packagePath.endsWith("tar.gz")) - { - updatingFailed(tr("Package not in tar.gz format, cannot install: %1").arg(packagePath)); - return false; - } - - proc.start("mv", {packagePath, outputDir}); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Package %1 cannot be installed, because cannot move it to directory: %2").arg(packagePath, outputDir)); - return false; - } - - QString fileName = packagePath.split("/").last(); - QString newPath = outputDir + "/" + fileName; - proc.start("tar", {"-xzf", newPath}); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Package %1 cannot be installed, because cannot unpack it: %2").arg(packagePath, readError(proc))); - return false; - } - - QProcess::execute("rm", {"-f", newPath}); - return true; -} -#endif - -#if defined(Q_OS_MACX) -bool UpdateManager::unpackToDirMac(const QString &packagePath, const QString &outputDir) -{ - QProcess proc; - proc.setWorkingDirectory(outputDir); - proc.setStandardOutputFile(QProcess::nullDevice()); - proc.setStandardErrorFile(QProcess::nullDevice()); - - if (!packagePath.endsWith("zip")) - { - updatingFailed(tr("Package not in zip format, cannot install: %1").arg(packagePath)); - return false; - } - - proc.start("unzip", {"-o", "-d", outputDir, packagePath}); - if (!waitForProcess(proc)) - { - updatingFailed(tr("Package %1 cannot be installed, because cannot unzip it to directory %2: %3") - .arg(packagePath, outputDir, readError(proc))); - return false; - } - - return true; -} -#endif - -#if defined(Q_OS_WIN32) -bool UpdateManager::unpackToDirWin(const QString& packagePath, const QString& outputDir) -{ - if (JlCompress::extractDir(packagePath, outputDir + "/..").isEmpty()) - { - updatingFailed(tr("Package %1 cannot be installed, because cannot unzip it to directory: %2").arg(packagePath, outputDir)); - return false; - } - - return true; -} -#endif - -void UpdateManager::handleStaticFail(const QString& errMsg) -{ - emit updatingFailed(errMsg); -} - -QString UpdateManager::getAppDirPath() const -{ - static QString appDir; - if (appDir.isNull()) - { - appDir = qApp->applicationDirPath(); -#ifdef Q_OS_MACX - QDir tmpAppDir(appDir); - tmpAppDir.cdUp(); - tmpAppDir.cdUp(); - appDir = tmpAppDir.absolutePath(); -#endif - } - return appDir; -} - -bool UpdateManager::moveDir(const QString& src, const QString& dst, bool contentsOnly) -{ - // If we're doing a rename in the very same parent directory then we don't want - // the 'move between partitions' to be involved, cause any failure to rename - // is due to permissions or file lock. - QFileInfo srcFi(src); - QFileInfo dstFi(dst); - bool sameParentDir = (srcFi.dir() == dstFi.dir()); - - QDir dir; - if (contentsOnly) - { - QString localSrc; - QString localDst; - QDir srcDir(src); - for (const QFileInfo& entry : srcDir.entryInfoList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot|QDir::Hidden|QDir::System)) - { - localSrc = entry.absoluteFilePath(); - localDst = dst + "/" + entry.fileName(); - if (!dir.rename(localSrc, localDst) && (sameParentDir || !renameBetweenPartitions(localSrc, localDst))) - { - staticUpdatingFailed(tr("Could not rename directory %1 to %2.").arg(localSrc, localDst)); - return false; - } - } - } - else - { - if (!dir.rename(src, dst) && (sameParentDir || !renameBetweenPartitions(src, dst))) - { - staticUpdatingFailed(tr("Could not rename directory %1 to %2.").arg(src, dst)); - return false; - } - } - - return true; -} - -bool UpdateManager::deleteDir(const QString& path) -{ - QDir dir(path); - if (!dir.removeRecursively()) - { - staticUpdatingFailed(tr("Could not delete directory %1.").arg(path)); - return false; - } - - return true; -} - -bool UpdateManager::execCmd(const QString& cmd, const QStringList& args, QString* errorMsg) -{ - QProcess proc; - proc.start(cmd, args); - QString cmdString = QString("%1 \"%2\"").arg(cmd, args.join("\\\" \\\"")); - - if (!waitForProcess(proc)) - { - if (errorMsg) - *errorMsg = tr("Error executing update command: %1\nError message: %2").arg(cmdString).arg(readError(proc)); - - return false; - } - - return true; -} - -void UpdateManager::setRetryFunction(const RetryFunction &value) -{ - retryFunction = value; -} - -bool UpdateManager::doRequireAdminPrivileges() -{ - QString appDirPath = getAppDirPath(); - QDir appDir(appDirPath); - bool isWritable = isWritableRecursively(appDir.absolutePath()); - - appDir.cdUp(); - QFileInfo fi(appDir.absolutePath()); - isWritable &= fi.isWritable(); - - if (isWritable) - { - QDir backupDir(getBackupDir(appDirPath)); - QString backupDirName = backupDir.dirName(); - backupDir.cdUp(); - if (backupDir.mkdir(backupDirName)) - backupDir.rmdir(backupDirName); - else - isWritable = false; - } - - return !isWritable; -} - -void UpdateManager::finished(QNetworkReply* reply) -{ - if (reply == updatesCheckReply) - { - updatesCheckReply = nullptr; - handleAvailableUpdatesReply(reply); - return; - } - - if (reply == updatesGetUrlsReply) - { - updatesGetUrlsReply = nullptr; - handleUpdatesMetadata(reply); + if (results.trimmed().isEmpty()) { + emit noUpdatesAvailable(); return; } - if (reply == updatesGetReply) - { - handleDownloadReply(reply); - if (reply == updatesGetReply) // if no new download is requested - updatesGetReply = nullptr; + QRegularExpression re(R"(\<update\s+([^\>]+)\>)"); + QRegularExpression versionRe(R"(version\=\"([\d\.]+)\")"); + QRegularExpression nameRe(R"(name\=\"([^\"]+)\")"); - return; - } -} - -void UpdateManager::handleDownloadReply(QNetworkReply* reply) -{ - if (reply->error() != QNetworkReply::NoError) + QRegularExpressionMatchIterator reIter = re.globalMatch(results); + QString updateNode; + UpdateEntry theUpdate; + QList<UpdateEntry> updates; + while (reIter.hasNext()) { - updatingFailed(tr("An error occurred while downloading updates: %1. Updating aborted.").arg(reply->errorString())); - reply->deleteLater(); - return; + updateNode = reIter.next().captured(1); + theUpdate.version = versionRe.match(updateNode).captured(1); + theUpdate.compontent = nameRe.match(updateNode).captured(1); + updates << theUpdate; } - totalPercent = (totalDownloadsCount - updatesToDownload.size()) * 100 / (totalDownloadsCount + 1); - - readDownload(); - currentDownloadFile->close(); - - safe_delete(currentDownloadFile); - - reply->deleteLater(); - downloadUpdates(); -} - -void UpdateManager::downloadProgress(qint64 bytesReceived, qint64 totalBytes) -{ - int perc; - if (totalBytes < 0) - perc = -1; - else if (totalBytes == 0) - perc = 100; + if (updates.isEmpty()) + emit noUpdatesAvailable(); else - perc = bytesReceived * 100 / totalBytes; - - emit updatingProgress(currentJobTitle, perc, totalPercent); -} - -void UpdateManager::readDownload() -{ - currentDownloadFile->write(updatesGetReply->readAll()); + emit updatesAvailable(updates); } #endif // PORTABLE_CONFIG |
