aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp')
-rw-r--r--SQLiteStudio3/coreSQLiteStudio/services/updatemanager.cpp1034
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