1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
|
#include "applicationupdater.h"
#include <QDir>
#include <QFile>
#include <QProcess>
#include <QFileInfo>
#include <QStandardPaths>
#include <QDesktopServices>
#include <QCoreApplication>
#include <QLoggingCategory>
#include "preferences.h"
#include "remotefilefetcher.h"
Q_LOGGING_CATEGORY(CATEGORY_SELFUPDATES, "SFU")
using namespace Flipper;
using namespace Updates;
ApplicationUpdater::ApplicationUpdater(QObject *parent):
QObject(parent),
m_state(Idle),
m_progress(0)
{}
void ApplicationUpdater::reset()
{
setState(Idle);
setProgress(0);
}
ApplicationUpdater::State ApplicationUpdater::state() const
{
return m_state;
}
double ApplicationUpdater::progress() const
{
return m_progress;
}
// TODO: Handle -rcxx suffixes correctly
bool ApplicationUpdater::canUpdate(const Flipper::Updates::VersionInfo &versionInfo) const
{
const auto appDate = QDateTime::fromSecsSinceEpoch(APP_TIMESTAMP).date();
const auto appVersion = QStringLiteral(APP_VERSION);
const auto appCommit = QStringLiteral(APP_COMMIT);
if(!globalPrefs->checkApplicationUpdates()) {
return false;
} else if(versionInfo.date() > appDate) {
return true;
} else if(globalPrefs->applicationUpdateChannel() == QStringLiteral("development")) {
return (versionInfo.date() == appDate) && (versionInfo.number() != appCommit);
} else if(globalPrefs->applicationUpdateChannel() == QStringLiteral("release-candidate")) {
return VersionInfo::compare(versionInfo.number(), appVersion) > 0;
} else if(globalPrefs->applicationUpdateChannel() == QStringLiteral("release")) {
return VersionInfo::compare(versionInfo.number(), appVersion) > 0;
} else {
return false;
}
}
void ApplicationUpdater::installUpdate(const Flipper::Updates::VersionInfo &versionInfo)
{
#ifdef Q_OS_WINDOWS
const auto fileInfo = versionInfo.fileInfo(QStringLiteral("installer"), QStringLiteral("windows/amd64"));
#elif defined(Q_OS_MAC)
const auto fileInfo = versionInfo.fileInfo(QStringLiteral("dmg"), QStringLiteral("macos/amd64"));
#elif defined(Q_OS_LINUX)
const auto fileInfo = versionInfo.fileInfo(QStringLiteral("AppImage"), QStringLiteral("linux/amd64"));
#else
#error "Unsupported OS"
#endif
const auto fileName = QFileInfo(fileInfo.url()).fileName();
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MAC)
const auto filePath = QDir::temp().absoluteFilePath(fileName);
#elif defined(Q_OS_LINUX)
const auto filePath = QDir::current().absoluteFilePath(fileName);
#else
#error "Unsupported OS"
#endif
auto *file = new QFile(filePath + QStringLiteral(".part"));
auto *fetcher = new RemoteFileFetcher(this);
const auto cleanup = [=]() {
file->deleteLater();
fetcher->deleteLater();
};
connect(fetcher, &RemoteFileFetcher::finished, this, [=]() {
if(fetcher->isError()) {
qCWarning(CATEGORY_SELFUPDATES).noquote() << "Failed to download application update package:" << fetcher->errorString();
setState(ErrorOccured);
cleanup();
return;
}
qCInfo(CATEGORY_SELFUPDATES) << "Application update package has been downloaded.";
setState(Updating);
// IMPORTANT -- The file is closed automatically before renaming (https://doc.qt.io/qt-5/qfile.html#rename)
QFile oldFile(filePath);
if(oldFile.exists() && !oldFile.remove()) {
qCDebug(CATEGORY_SELFUPDATES).noquote() << "Failed to remove old update package:" << oldFile.fileName();
setState(ErrorOccured);
cleanup();
return;
} else if(!file->rename(filePath)) {
qCDebug(CATEGORY_SELFUPDATES).noquote() << "Failed to rename .part file:" << file->fileName();
setState(ErrorOccured);
file->remove();
cleanup();
return;
}
#if defined(Q_OS_LINUX)
const auto executable = QFile::Permission::ExeUser | QFile::Permission::ExeOwner |
QFile::Permission::ExeGroup | QFile::Permission::ExeOther;
file->setPermissions(file->permissions() | executable);
#endif
cleanup();
if(!performUpdate(filePath)) {
qCWarning(CATEGORY_SELFUPDATES) << "Failed to start the application update process.";
setState(ErrorOccured);
}
});
connect(fetcher, &RemoteFileFetcher::progressChanged, this, &ApplicationUpdater::setProgress);
if(!fetcher->fetch(fileInfo, file)) {
qCWarning(CATEGORY_SELFUPDATES) << "Failed to start downloading the update package.";
setState(ErrorOccured);
file->remove();
cleanup();
} else {
qCWarning(CATEGORY_SELFUPDATES) << "Downloading the application update package...";
setState(Downloading);
}
}
void ApplicationUpdater::setState(ApplicationUpdater::State state)
{
if(m_state == state) {
return;
}
m_state = state;
emit stateChanged();
}
void ApplicationUpdater::setProgress(double progress)
{
if(qFuzzyCompare(m_progress, progress)) {
return;
}
m_progress = progress;
emit progressChanged();
}
bool ApplicationUpdater::performUpdate(const QString &path)
{
const auto exitApplication = []() {
qCInfo(CATEGORY_SELFUPDATES) << "Update started, exiting the application...";
QCoreApplication::exit(0);
};
#if defined(Q_OS_WINDOWS)
const auto success = QDesktopServices::openUrl(QUrl::fromLocalFile(path));
if(success) exitApplication();
return success;
#elif defined(Q_OS_MAC)
auto *mountDmg = new QProcess(this);
mountDmg->setProgram(QStringLiteral("open"));
mountDmg->setArguments({path});
connect(mountDmg, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[=](int exitCode, QProcess::ExitStatus exitStatus) {
mountDmg->deleteLater();
if(!exitCode && exitStatus == QProcess::NormalExit) {
exitApplication();
} else {
qCWarning(CATEGORY_SELFUPDATES) << "Failed to open the disk image.";
}
});
mountDmg->start();
return mountDmg->error() == QProcess::UnknownError; //Really? no NoError code?
#elif defined(Q_OS_LINUX)
const auto success = QProcess::startDetached(path);
if(success) exitApplication();
return success;
#else
#error "Unsupported OS"
#endif
}
|