Files
edgetx-gx12/companion/src/updates/updateinterface.cpp
T
2022-09-06 09:29:41 +10:00

1568 lines
40 KiB
C++

/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program 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.
*/
#include "updateinterface.h"
#include "appdata.h"
#include "minizinterface.h"
#include "helpers.h"
#include "updatefirmware.h"
#include "updatecompanion.h"
#include "updatesdcard.h"
#include "updatesounds.h"
#include "updatethemes.h"
#include "updatemultiprotocol.h"
#include <QMessageBox>
#include <QtNetwork/QNetworkProxyFactory>
#include <QJsonDocument>
#include <QFileDialog>
#include <QFileInfo>
#include <QRegularExpression>
/*
UpdateParameters
*/
UpdateParameters::UpdateParameters(QWidget * parent) :
QWidget(parent)
{
data.flags = 0;
data.decompressDirUseDwnld = true;
data.updateDirUseSD = true;
}
UpdateParameters& UpdateParameters::operator=(const UpdateParameters& source)
{
// Perform a deep copy
// check for self-assignment
if (this != &source) {
data.flags = source.data.flags;
data.currentRelease = source.data.currentRelease;
data.updateRelease = source.data.updateRelease;
data.downloadDir = source.data.downloadDir;
data.decompressDirUseDwnld = source.data.decompressDirUseDwnld;
data.decompressDir = source.data.decompressDir;
data.updateDirUseSD = source.data.updateDirUseSD;
data.updateDir = source.data.updateDir;
while (!data.assets.isEmpty()) {
data.assets.removeLast();
}
for (int i = 0; i < source.data.assets.size(); i++) {
data.assets.append(source.data.assets[i]);
}
}
return *this;
}
UpdateParameters::AssetParams & UpdateParameters::addAsset()
{
AssetParams ap;
ap.flags = 0;
ap.filterType = UFT_None;
ap.maxExpected = -1;
ap.copyFilterType = UFT_None;
data.assets.append(ap);
return data.assets[data.assets.size() - 1];
}
// static
QString UpdateParameters::buildFilterPattern(const UpdateParameters::UpdateFilterType filterType, const QString & filter)
{
QString pattern = filter;
switch ((int)filterType) {
case UpdateParameters::UFT_Exact:
pattern.prepend("^");
pattern.append("$");
break;
case UpdateParameters::UFT_Startswith:
pattern.prepend("^");
break;
case UpdateParameters::UFT_Endswith:
pattern.append("$");
break;
case UpdateParameters::UFT_Contains:
case UpdateParameters::UFT_Expression:
break;
default:
pattern = "";
}
return pattern;
}
// static
QStringList UpdateParameters::updateFilterTypeList()
{
return { tr("None"), tr("Exact"), tr("Startswith"), tr("Endswith"), tr("Contains"), tr("Expression") };
}
// static
QString UpdateParameters::updateFilterTypeToString(UpdateFilterType uft)
{
return updateFilterTypeList().value(uft, CPN_STR_UNKNOWN_ITEM);
}
/*
UpdateInterface
*/
UpdateInterface::UpdateInterface(QWidget * parent) :
QWidget(parent),
reply(nullptr),
buffer(new QByteArray()),
file(nullptr)
{
progress = nullptr;
settingsIdx = -1;
QNetworkProxyFactory::setUseSystemConfiguration(true);
releases = new ReleasesMetaData(this);
assets = new AssetsMetaData(this);
dfltParams = new UpdateParameters(this);
runParams = new UpdateParameters(this);
getRadioProfileSettings();
setReleasesNightlyName("");
dfltParams->data.flags = UPDFLG_Common;
resultsPerPage = -1;
initParamFolders(dfltParams);
}
UpdateInterface::~UpdateInterface()
{
if (reply)
delete reply;
delete buffer;
if (file)
delete file;
delete releases;
delete assets;
delete dfltParams;
delete runParams;
}
bool UpdateInterface::autoUpdate(ProgressWidget * progress)
{
this->progress = progress;
if (dfltParams->data.flags & UPDFLG_Update && !isLatestRelease())
return update();
return true;
}
bool UpdateInterface::manualUpdate(ProgressWidget * progress)
{
this->progress = progress;
if (runParams->data.flags & UPDFLG_Update)
return update();
return true;
}
bool UpdateInterface::update()
{
if (progress) {
progress->setInfo(tr("Processing updates for: %1").arg(name));
progress->setValue(0);
progress->setMaximum(100);
}
reportProgress(tr("Processing updates for: %1").arg(name), QtInfoMsg);
if ((runParams->data.flags & UPDFLG_Preparation) && !preparation()) {
reportProgress(tr("%1 step preparation failed").arg(name), QtCriticalMsg);
return false;
}
if ((runParams->data.flags & UPDFLG_Download) && !download()) {
reportProgress(tr("%1 step download failed").arg(name), QtCriticalMsg);
return false;
}
if ((runParams->data.flags & UPDFLG_Decompress) && !decompress()) {
reportProgress(tr("%1 step decompress failed").arg(name), QtCriticalMsg);
return false;
}
if ((runParams->data.flags & UPDFLG_CopyDest) && !copyToDestination()) {
reportProgress(tr("%1 step copy to destination failed").arg(name), QtCriticalMsg);
return false;
}
if ((runParams->data.flags & UPDFLG_Housekeeping) && !housekeeping()) {
reportProgress(tr("%1 step housekeeping failed").arg(name), QtCriticalMsg);
return false;
}
if ((runParams->data.flags & UPDFLG_AsyncInstall) && !asyncInstall()) {
reportProgress(tr("%1 step start async failed").arg(name), QtCriticalMsg);
return false;
}
reportProgress(tr("%1 update successful").arg(name), QtInfoMsg);
QMessageBox::information(this, CPN_STR_APP_NAME, tr("%1 update successful").arg(name));
return true;
}
// static
QString UpdateInterface::updateFlagsToString(UpdateFlags val)
{
switch ((int)val) {
case UPDFLG_None:
return "None";
case UPDFLG_Update:
return "Update";
case UPDFLG_Locked:
return "Locked";
case UPDFLG_Preparation:
return "Preparation";
case UPDFLG_Download:
return "Download";
case UPDFLG_Decompress:
return "Decompress";
case UPDFLG_CopyDest:
return "Copy to Destination";
case UPDFLG_CopyFiles:
return "Copy Files";
case UPDFLG_CopyStructure:
return "Copy Structure";
case UPDFLG_Housekeeping:
return "Housekeeping";
case UPDFLG_AsyncInstall:
return "Async Install";
case UPDFLG_DelDownloads:
return "Delete Downloads";
default:
return CPN_STR_UNKNOWN_ITEM;
}
}
// static
QString UpdateInterface::downloadDataTypeToString(DownloadDataType val)
{
switch ((int)val) {
case DDT_Binary:
return "Binary";
case DDT_Content:
return "Content";
case DDT_MetaData:
return "Meta Data";
default:
return CPN_STR_UNKNOWN_ITEM;
}
}
// static
QString UpdateInterface::semanticVersion(QString version)
{
QStringList strl = version.split(".");
while (strl.count() < 4) {
strl.append("0");
}
return strl.join(".");
}
void UpdateInterface::reportProgress(const QString & text, const int type)
{
if (logLevel == QtDebugMsg || (logLevel == QtInfoMsg && type > QtDebugMsg) || (type < QtInfoMsg && type >= logLevel)) {
if (progress)
progress->addMessage(text, type);
else
qDebug() << text;
}
if (type == QtCriticalMsg || type == QtFatalMsg)
criticalMsg(text);
}
void UpdateInterface::progressMessage(const QString & text)
{
if (progress)
progress->setInfo(text);
else
qDebug() << text;
}
void UpdateInterface::criticalMsg(const QString & msg)
{
QMessageBox::critical(nullptr, CPN_STR_APP_NAME % ": " % tr("Update Interface"), msg);
}
void UpdateInterface::setName(QString name)
{
this->name = name;
setSettingsIdx();
currentRelease();
}
void UpdateInterface::setRepo(QString repo)
{
releases->setRepo(repo);
assets->setRepo(repo);
}
void UpdateInterface::initParamFolders(UpdateParameters * params)
{
if (g.downloadDir().trimmed().isEmpty())
g.downloadDirReset(true);
params->data.downloadDir = g.downloadDir().trimmed();
params->data.decompressDirUseDwnld = g.decompressDirUseDwnld();
if (params->data.decompressDirUseDwnld)
params->data.decompressDir = params->data.downloadDir;
else {
if (g.decompressDir().trimmed().isEmpty())
g.decompressDirReset(true);
params->data.decompressDir = g.decompressDir().trimmed();
}
params->data.updateDirUseSD = g.updateDirUseSD();
if (params->data.updateDirUseSD && (!g.currentProfile().sdPath().trimmed().isEmpty()))
params->data.updateDir = g.currentProfile().sdPath().trimmed();
else {
if (g.updateDir().trimmed().isEmpty())
g.updateDirReset(true);
params->data.updateDir = g.updateDir().trimmed();
}
}
void UpdateInterface::resetRunEnvironment()
{
progress = nullptr;
logLevel = g.updLogLevel();
*runParams = *dfltParams;
runParams->data.flags &= ~UPDFLG_Update;
if (g.updDelDownloads())
runParams->data.flags |= UpdateInterface::UPDFLG_DelDownloads;
else
runParams->data.flags &= ~UpdateInterface::UPDFLG_DelDownloads;
}
int UpdateInterface::getSettingsIdx()
{
int idx = g.getComponentIndex(name);
if (idx < 0) {
for (idx = 0; idx < MAX_COMPONENTS && g.component[idx].existsOnDisk(); idx++)
;
if (idx >= MAX_COMPONENTS) {
reportProgress(tr("No free slot to save interface settings!"), QtCriticalMsg);
idx = -1;
}
else {
g.component[idx].init();
g.component[idx].name(name);
}
}
return idx;
}
void UpdateInterface::setSettingsIdx()
{
const int idx = getSettingsIdx();
settingsIdx = idx;
releases->setSettingsIndex(idx);
}
void UpdateInterface::getRadioProfileSettings()
{
// eg edgetx-ts16s-en
QStringList strl = QStringList(g.currentProfile().fwType().split('-'));
if (strl.size() < 1) {
reportProgress(tr("Settings radio profile firmware type unexpected format"), QtWarningMsg);
return;
}
if (strl.size() > 1) {
fwFlavour = strl.at(1);
fwFlavour = fwFlavour.replace('+', 'p');
}
if (strl.size() > 2)
language = strl.at(2);
}
void UpdateInterface::clearRelease()
{
g.component[settingsIdx].clearRelease();
currentRelease();
}
const QString UpdateInterface::currentRelease()
{
dfltParams->data.currentRelease = g.component[settingsIdx].release();
runParams->data.currentRelease = dfltParams->data.currentRelease;
return dfltParams->data.currentRelease;
}
const QString UpdateInterface::latestRelease()
{
if (repoReleasesMetaData()) {
releases->getSetId();
if (releases->id())
dfltParams->data.updateRelease = releases->name();
else
dfltParams->data.updateRelease = "";
}
return dfltParams->data.updateRelease;
}
const QString UpdateInterface::updateRelease()
{
if (runParams->data.updateRelease.isEmpty()) {
if (dfltParams->data.updateRelease.isEmpty())
latestRelease();
runParams->data.updateRelease = dfltParams->data.updateRelease;
}
return runParams->data.updateRelease;
}
const bool UpdateInterface::isUpdateAvailable()
{
if (g.component[settingsIdx].checkForUpdate())
return !isLatestRelease();
else {
return false;
}
}
const bool UpdateInterface::isLatestVersion(const QString & installed, const QString & latest)
{
QStringList a = semanticVersion(installed).split(".");
QStringList b = semanticVersion(latest).split(".");
// not equal comparision used to force update if client upgraded and the release is subsequently withdrawn or subscribed to nightlies
for (int i = 0; i < 4; i++) {
if (a.at(i) != b.at(i)) {
return false;
}
}
return true;
}
const bool UpdateInterface::isLatestRelease()
{
// subsequent processes rely on this call being successful so advise nothing to do
if (!repoReleasesMetaData()) {
return true;
}
QString currentVer = currentVersion();
QString latestVer = releases->version();
// nightlies often have the same version so also check id
if (isLatestVersion(currentVer, latestVer) && g.component[settingsIdx].id() == releases->id()) {
return true;
}
else {
return false;
}
}
const QString UpdateInterface::currentVersion()
{
return g.component[settingsIdx].version();
}
const QStringList UpdateInterface::getReleases()
{
repoReleasesMetaData();
return releases->list();
}
bool UpdateInterface::repoReleasesMetaData()
{
if (releases->refreshRequired()) {
if (!downloadReleasesMetaData()) {
reportProgress(tr("Unable to download release meta data"), QtDebugMsg);
releases->setId(0);
return false;
}
}
releases->getSetId();
return true;
}
bool UpdateInterface::repoReleaseAssetsMetaData()
{
if (!downloadReleaseAssetsMetaData(releases->id())) {
reportProgress(tr("Unable to download release channel assets information"), QtDebugMsg);
return false;
}
return true;
}
bool UpdateInterface::setRunFolders()
{
QString fldr(releases->name());
fldr = fldr.replace("\"", "");
fldr = fldr.replace("\'", "");
fldr = fldr.replace(" ", "_");
fldr = fldr.replace("(", "_");
fldr = fldr.replace(")", "_");
fldr = fldr.replace("[", "_");
fldr = fldr.replace("]", "_");
downloadDir = QString("%1/%2/%3").arg(runParams->data.downloadDir).arg(name).arg(fldr);
if (!checkCreateDirectory(downloadDir, UPDFLG_Download))
return false;
reportProgress(tr("Download directory: %1").arg(downloadDir), QtDebugMsg);
decompressDir = QString("%1/%2/%3").arg(runParams->data.decompressDir).arg(name).arg(fldr);
if (!checkCreateDirectory(decompressDir, UPDFLG_Decompress))
return false;
reportProgress(tr("Decompress directory: %1").arg(decompressDir), QtDebugMsg);
updateDir = runParams->data.updateDir;
if (!checkCreateDirectory(updateDir, UPDFLG_CopyDest))
return false;
reportProgress(tr("Update directory: %1").arg(updateDir), QtDebugMsg);
return true;
}
bool UpdateInterface::checkCreateDirectory(const QString & dir, const UpdateFlags flag)
{
if (runParams->data.flags & flag) {
if (dir.isEmpty()) {
reportProgress(tr("%1 directory not configured in application settings!").arg(updateFlagsToString(flag)), QtCriticalMsg);
return false;
}
else if (!QDir().mkpath(dir)) {
reportProgress(tr("Failed to create %1 directory %2!").arg(updateFlagsToString(flag)).arg(dir), QtCriticalMsg);
return false;
}
}
return true;
}
bool UpdateInterface::downloadReleasesMetaData()
{
progressMessage(tr("Download releases metadata"));
downloadMetaData(MDT_Releases, releases->urlReleases());
return downloadSuccess;
}
bool UpdateInterface::downloadReleaseLatestMetaData()
{
progressMessage(tr("Download latest release metadata"));
downloadMetaData(MDT_Release, releases->urlReleaseLatest());
return downloadSuccess;
}
bool UpdateInterface::downloadReleaseMetaData(const int releaseId)
{
progressMessage(tr("Download release %1 metadata").arg(releaseId));
releases->setId(releaseId);
downloadMetaData(MDT_Release, releases->urlRelease());
return downloadSuccess;
}
bool UpdateInterface::downloadReleaseAssetsMetaData(const int releaseId)
{
progressMessage(tr("Download release %1 assets metadata").arg(releaseId));
downloadMetaData(MDT_ReleaseAssets, assets->urlReleaseAssets(releaseId, resultsPerPage));
return downloadSuccess;
}
bool UpdateInterface::downloadAssetMetaData(const int assetId)
{
progressMessage(tr("Download asset %1 metadata").arg(assetId));
assets->setId(assetId);
downloadMetaData(MDT_Asset, assets->urlAsset());
return downloadSuccess;
}
void UpdateInterface::downloadMetaData(const MetaDataType mdt, const QString & url)
{
reportProgress(tr("Download metadata for: %1").arg(metaDataTypeToString(mdt).toLower()));
download(DDT_MetaData, mdt, url, GH_ACCEPT_HEADER_METADATA, QString());
}
bool UpdateInterface::downloadTextFileToBuffer(const QString & path)
{
progressMessage(tr("Download file %1").arg(path));
reportProgress(tr("Download file: %1").arg(path), QtInfoMsg);
downloadFileToBuffer(assets->urlContent(path));
return downloadSuccess;
}
void UpdateInterface::downloadFileToBuffer(const QString & url)
{
download(DDT_Content, 0, url, GH_ACCEPT_HEADER_RAW, QString());
}
void UpdateInterface::download(const DownloadDataType type, const int subtype, const QString & urlStr,
const char * header, const QString & filePath)
{
if (progress) {
progress->setValue(0);
progress->setMaximum(100);
}
buffer->clear();
downloadSuccess = false;
if (type == DDT_SaveToFile) {
file = new QFile(filePath);
if (!file->open(QIODevice::WriteOnly)) {
reportProgress(tr("Unable to open the download file %1 for writing.\nError: %2").arg(filePath).arg(file->errorString()), QtCriticalMsg);
return;
}
}
else
file = nullptr;
url.setUrl(urlStr);
if (!url.isValid()) {
reportProgress(tr("Invalid URL: %1").arg(urlStr), QtCriticalMsg);
return;
}
else
reportProgress(tr("URL: %1").arg(urlStr), QtDebugMsg);
request.setUrl(url);
request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork);
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
request.setRawHeader(QByteArray("Accept"), QByteArray(header));
reply = manager.get(request);
connect(reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), [=] (QNetworkReply::NetworkError code) {
// leave it to the finished slot to deal with error condition
reportProgress(tr("Network error has occurred. Error code: %1").arg(code), QtDebugMsg);
});
connect(reply, &QNetworkReply::readyRead, [=]() {
if (type == DDT_SaveToFile) {
file->write(reply->readAll());
}
else {
const QByteArray qba = reply->readAll();
buffer->append(qba);
}
});
connect(reply, &QNetworkReply::finished, [&]() {
onDownloadFinished(reply, type, subtype);
});
connect(reply, &QNetworkReply::downloadProgress, [=](const qint64 bytesRead, const qint64 totalBytes) {
if (progress) {
progress->setMaximum(totalBytes);
progress->setValue(bytesRead);
}
});
while (!reply->isFinished()) {
qApp->processEvents();
}
}
void UpdateInterface::onDownloadFinished(QNetworkReply * reply, DownloadDataType type, int subtype)
{
if (type == DDT_SaveToFile) {
file->flush();
file->close();
}
if (progress)
progress->setValue(progress->maximum());
if (reply->error()) {
reportProgress(tr("Unable to download %1.\nError:%2\n%3").arg(downloadDataTypeToString(type)).arg(reply->error()).arg(reply->errorString()), QtCriticalMsg);
if (type == DDT_SaveToFile) {
file->remove();
delete file;
file = nullptr;
}
return;
}
if (type == DDT_MetaData) {
parseMetaData(subtype);
}
else if (type == DDT_SaveToFile) {
delete file;
file = nullptr;
downloadSuccess = true;
}
else {
downloadSuccess = true;
}
}
void UpdateInterface::parseMetaData(int mdt)
{
reportProgress(tr("Parse and load data models with metadata"), QtDebugMsg);
QJsonDocument *json = new QJsonDocument();
if (!convertDownloadToJson(json)) {
return;
}
switch (mdt) {
case MDT_Release:
case MDT_Releases:
releases->parseMetaData(mdt, json);
break;
case MDT_Asset:
case MDT_ReleaseAssets:
assets->parseMetaData(mdt, json);
break;
}
delete json;
downloadSuccess = true;
}
bool UpdateInterface::convertDownloadToJson(QJsonDocument * json)
{
QJsonParseError res;
*json = QJsonDocument::fromJson(*buffer, &res);
if (res.error || json->isNull()) {
reportProgress(tr("Unable to convert downloaded data to json format.\nError:%1\n%2").arg(res.error).arg(res.errorString()), QtCriticalMsg);
return false;
}
return true;
}
bool UpdateInterface::getSetAssets(const int flags, const UpdateParameters::UpdateFilterType assetsFilterType, const QString & assetsFilter, const int maxAssetsExpected,
const QString & subDirectory, const UpdateParameters::UpdateFilterType copyFilterType, const QString & copyFilter)
{
QString pattern(UpdateParameters::buildFilterPattern(assetsFilterType, assetsFilter));
assets->setFilterPattern(pattern);
reportProgress(tr("Asset filter applied: %1").arg(pattern), QtDebugMsg);
if (assets->count() < 0) {
reportProgress(tr("No assets not found in release '%1' using filter pattern '%2'").arg(releases->name().arg(assetsFilter)), QtCriticalMsg);
return false;
}
else if (maxAssetsExpected > -1 && assets->count() > maxAssetsExpected) {
reportProgress(tr("%1 assets found when %2 expected in release '%3' using filter pattern '%4'")
.arg(assets->count()).arg(maxAssetsExpected).arg(releases->name().arg(assetsFilter)), QtCriticalMsg);
return false;
}
reportProgress(tr("Assets found: %1").arg(assets->count()), QtDebugMsg);
for (int i = 0; i < assets->count(); i++) {
assets->getSetId(i);
if (!assets->setFlags(flags)) {
reportProgress(tr("Unable to set processing flags for asset %1").arg(assets->filename()), QtCriticalMsg);
return false;
}
if (!assets->setSubDirectory(subDirectory)) {
reportProgress(tr("Unable to set sub directory for asset %1").arg(assets->filename()), QtCriticalMsg);
return false;
}
if (!assets->setCopyFilter(UpdateParameters::buildFilterPattern(copyFilterType, copyFilter))) {
reportProgress(tr("Unable to set copy filter for asset %1").arg(assets->filename()), QtCriticalMsg);
return false;
}
}
return true;
}
bool UpdateInterface::downloadFlaggedAssets()
{
assets->setFilterFlags(UPDFLG_Download);
reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_Download)).arg(assets->count()), QtDebugMsg);
for (int i = 0; i < assets->count(); ++i) {
if (!downloadAsset(i))
return false;
}
return true;
}
bool UpdateInterface::downloadAsset(int row)
{
assets->getSetId(row);
downloadBinaryToFile(assets->urlAsset(), assets->filename());
return downloadSuccess;
}
void UpdateInterface::downloadBinaryToFile(const QString & url, const QString & filename)
{
downloadSuccess = false;
QFileInfo f(QString("%1/A%2/%3").arg(downloadDir).arg(assets->id()).arg(filename));
progressMessage(tr("Downloading: %1").arg(filename));
reportProgress(tr("Download: %1").arg(filename), QtInfoMsg);
if (f.exists()) {
reportProgress(tr("File exists: %1").arg(filename), QtDebugMsg);
int reply = QMessageBox::question(this, CPN_STR_APP_NAME, tr("File %1 exists. Download again?").arg(filename),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No);
if (reply == QMessageBox::Cancel) {
reportProgress(tr("User action: Cancel"), QtDebugMsg);
downloadSuccess = false;
return;
}
else if (reply == QMessageBox::No) {
reportProgress(tr("User action: Use previous download"), QtDebugMsg);
downloadSuccess = true;
return;
}
else {
reportProgress(tr("User action: Download again"), QtDebugMsg);
}
}
else if (!QDir().mkpath(f.path())) {
reportProgress(tr("Failed to create directory %1!").arg(f.path()), QtCriticalMsg);
return;
}
download(DDT_SaveToFile, 0, url, GH_ACCEPT_HEADER_BINARY, f.absoluteFilePath());
}
bool UpdateInterface::decompressFlaggedAssets()
{
assets->setFilterFlags(UPDFLG_Decompress);
reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_Decompress)).arg(assets->count()), QtDebugMsg);
for (int i = 0; i < assets->count(); ++i) {
if (!decompressAsset(i))
return false;
}
return true;
}
bool UpdateInterface::decompressAsset(int row)
{
assets->getSetId(row);
if (!(assets->flags() & UPDFLG_Decompress))
return true;
QString filename = assets->filename();
if (filename.isEmpty())
return false;
QFileInfo f(QString("%1/A%2/%3").arg(downloadDir).arg(assets->id()).arg(assets->filename()));
if (!f.exists())
return false;
reportProgress(tr("Decompress: %1").arg(filename), QtInfoMsg);
decompressArchive(f.absoluteFilePath(), QString("%1/A%2").arg(decompressDir).arg(assets->id()));
return true;
}
bool UpdateInterface::decompressArchive(const QString & archivePath, const QString & destDir)
{
QString dest = destDir;
if (!dest.endsWith("/"))
dest.append("/");
dest.append(QFileInfo(archivePath).completeBaseName());
MinizInterface mIface(progress, MinizInterface::PCM_SIZE, logLevel);
if (!mIface.unzipArchiveToPath(archivePath, dest)) {
criticalMsg(tr("Failed to decompress %1").arg(archivePath));
return false;
}
return true;
}
bool UpdateInterface::copyFlaggedAssets()
{
assets->setFilterFlags(UPDFLG_CopyDest);
reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_CopyDest)).arg(assets->count()), QtDebugMsg);
for (int i = 0; i < assets->count(); ++i) {
assets->getSetId(i);
if (!copyAsset())
return false;
}
return true;
}
bool UpdateInterface::copyAsset()
{
if (assets->flags() & UPDFLG_CopyStructure)
return copyStructure();
else if (assets->flags() & UPDFLG_CopyFiles)
return copyFiles();
return true;
}
bool UpdateInterface::copyStructure()
{
reportProgress(tr("Copy directory structure"), QtInfoMsg);
if (progress) {
progress->setValue(0);
progress->setMaximum(100);
}
QDir srcPath(QString("%1/A%2/%3").arg(decompressDir).arg(assets->id()).arg(QFileInfo(assets->filename()).completeBaseName()));
if (!srcPath.exists()) {
reportProgress(tr("Decompressed file structure not found at %1").arg(QDir::toNativeSeparators(srcPath.path())), QtCriticalMsg);
return false;
}
reportProgress(tr("Copy file structure from: %1").arg(QDir::toNativeSeparators(srcPath.path())), QtDebugMsg);
QString baseDir = updateDir;
if (!assets->subDirectory().isEmpty())
baseDir.append("/" % assets->subDirectory());
// create directory structure first as there is no order to QDirIterator if both files and directories selected
// also supports creation of empty directories
QDirIterator itdirs(srcPath.path(), QDir::AllDirs | QDir::NoDot | QDir::NoDotDot, QDirIterator::Subdirectories);
int cnt = 0;
while (itdirs.hasNext())
{
QFileInfo src(itdirs.next());
QString subPath(src.absoluteFilePath());
subPath.replace(srcPath.path(), "");
QString dir = baseDir % subPath;
if (!QDir().mkpath(dir)) {
reportProgress(tr("Failed to create directory %1").arg(dir), QtCriticalMsg);
return false;
}
reportProgress(tr("Check/create directory: %1").arg(dir), QtDebugMsg);
cnt++;
}
reportProgress(tr("Directories checked/created: %1").arg(cnt), QtInfoMsg);
QRegularExpression filter(assets->copyFilter(), QRegularExpression::CaseInsensitiveOption);
reportProgress(tr("Copy filter pattern: %1").arg(filter.pattern()), QtDebugMsg);
QDirIterator itcnt(srcPath.path(), QDir::Files, QDirIterator::Subdirectories);
cnt = 0;
reportProgress(tr("Calculating number of files"), QtDebugMsg);
while (itcnt.hasNext())
{
QFileInfo src(itcnt.next());
if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter)))
continue;
cnt++;
}
if (progress)
progress->setMaximum(cnt);
QDirIterator itfiles(srcPath.path(), QDir::Files, QDirIterator::Subdirectories);
cnt = 0;
while (itfiles.hasNext())
{
QFileInfo src(itfiles.next());
if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter)))
continue;
QString subPath(src.absoluteFilePath());
subPath.replace(srcPath.path(), "");
QFile dest(baseDir % subPath);
if (dest.exists()) {
if (!dest.remove()) {
reportProgress(tr("Failed to delete existing file %1").arg(dest.fileName()), QtCriticalMsg);
return false;
}
}
if (!QFile::copy(src.absoluteFilePath(), dest.fileName())) {
reportProgress(tr("Failed to copy file %1").arg(src.absoluteFilePath()), QtCriticalMsg);
return false;
}
reportProgress(tr("Copied %1 to %2").arg(src.fileName()).arg(dest.fileName()), QtDebugMsg);
cnt++;
if (progress)
progress->setValue(cnt);
}
if (progress)
progress->setValue(progress->maximum());
reportProgress(tr("Files copied: %1").arg(cnt), QtInfoMsg);
return true;
}
bool UpdateInterface::copyFiles()
{
reportProgress(tr("Copy files"), QtInfoMsg);
if (progress) {
progress->setValue(0);
progress->setMaximum(100);
}
QString srcPath;
if (assets->flags() & UPDFLG_Decompress)
srcPath = QString("%1/A%2/%3").arg(decompressDir).arg(assets->id()).arg(QFileInfo(assets->filename()).completeBaseName());
else if (assets->flags() & UPDFLG_Download)
srcPath = QString("%1/A%2").arg(downloadDir).arg(assets->id());
else {
reportProgress(tr("Unable to determine source directory for asset %1").arg(assets->filename()), QtCriticalMsg);
return false;
}
reportProgress(tr("Copy files from %1").arg(srcPath), QtDebugMsg);
QString destPath = updateDir;
if (!assets->subDirectory().isEmpty())
destPath.append("/" % assets->subDirectory());
if (!QDir().mkpath(destPath)) {
reportProgress(tr("Failed to create directory %1").arg(QDir::toNativeSeparators(destPath)), QtCriticalMsg);
return false;
}
reportProgress(tr("Check/create directory: %1").arg(destPath), QtDebugMsg);
reportProgress(tr("Directories checked/created: %1").arg(1), QtDebugMsg);
QRegularExpression filter(assets->copyFilter(), QRegularExpression::CaseInsensitiveOption);
reportProgress(tr("Copy filter pattern: %1").arg(filter.pattern()), QtDebugMsg);
QDirIterator itcnt(srcPath, QDir::Files, QDirIterator::Subdirectories);
int cnt = 0;
reportProgress(tr("Calculating number of files"), QtDebugMsg);
while (itcnt.hasNext())
{
QFileInfo src(itcnt.next());
if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter)))
continue;
cnt++;
}
if (progress)
progress->setMaximum(cnt);
QDirIterator itfiles(srcPath, QDir::Files, QDirIterator::Subdirectories);
cnt = 0;
while (itfiles.hasNext())
{
QFileInfo src(itfiles.next());
if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter)))
continue;
QFile dest(destPath % "/" % src.fileName());
if (dest.exists()) {
if (!dest.remove()) {
reportProgress(tr("Failed to delete existing file %1").arg(dest.fileName()), QtCriticalMsg);
return false;
}
reportProgress(tr("Deleted file: %1").arg(dest.fileName()), QtDebugMsg);
}
if (!QFile::copy(src.absoluteFilePath(), dest.fileName())) {
reportProgress(tr("Failed to copy file %1").arg(src.absoluteFilePath()), QtCriticalMsg);
return false;
}
reportProgress(tr("Copied %1 to %2").arg(src.fileName()).arg(dest.fileName()), QtDebugMsg);
cnt++;
if (progress)
progress->setValue(cnt);
}
if (!cnt) {
reportProgress(tr("No downloaded or decompressed files matched filter '%1'").arg(assets->copyFilter()), QtCriticalMsg);
return false;
}
if (progress)
progress->setValue(progress->maximum());
reportProgress(tr("Files copied: %1").arg(cnt), QtInfoMsg);
return true;
}
bool UpdateInterface::preparation()
{
progressMessage(tr("Preparing"));
int cnt = 0;
if (progress) {
progress->setValue(cnt);
progress->setMaximum(5);
}
if (!setRunFolders()) {
reportProgress(tr("Set run folders failed"), QtDebugMsg);
return false;
}
if (progress)
progress->setValue(++cnt);
if (!repoReleasesMetaData()) {
return false;
}
if (progress)
progress->setValue(++cnt);
if (!releases->getSetId(runParams->data.updateRelease)) {
reportProgress(tr("Set release id from update release name failed"), QtDebugMsg);
return false;
}
if (progress)
progress->setValue(++cnt);
if (!repoReleaseAssetsMetaData())
return false;
if (progress)
progress->setValue(++cnt);
if (!flagAssets())
return false;
if (progress)
progress->setValue(++cnt);
return true;
}
bool UpdateInterface::flagAssets()
{
progressMessage(tr("Flagging assets"));
for (int i = 0; i < runParams->data.assets.size(); i++) {
const UpdateParameters::AssetParams &ap = runParams->data.assets.at(i);
if (!getSetAssets(ap.flags, ap.filterType, ap.filter, ap.maxExpected, ap.destSubDir, ap.copyFilterType, ap.copyFilter))
return false;
}
return true;
}
bool UpdateInterface::download()
{
progressMessage(tr("Downloading assets"));
if (!downloadFlaggedAssets()) {
reportProgress(tr("Unable to download flagged assets"), QtDebugMsg);
return false;
}
return true;
}
bool UpdateInterface::decompress()
{
progressMessage(tr("Decompressing assets"));
if (!decompressFlaggedAssets()) {
reportProgress(tr("Unable to decompress flagged assets"), QtDebugMsg);
return false;
}
return true;
}
bool UpdateInterface::copyToDestination()
{
progressMessage(tr("Copying to destination"));
if (!copyFlaggedAssets()) {
reportProgress(tr("Unable to copy assets"), QtDebugMsg);
return false;
}
return true;
}
bool UpdateInterface::housekeeping()
{
progressMessage(tr("Housekeeping"));
int cnt = 0;
if (progress) {
progress->setValue(cnt);
progress->setMaximum(3);
}
if (!saveReleaseSettings()) {
reportProgress(tr("Failed to save release settings"), QtDebugMsg);
return false;
}
if (progress)
progress->setValue(++cnt);
if (runParams->data.flags & UPDFLG_DelDownloads) {
reportProgress(tr("Delete download directory: %1").arg(downloadDir), QtDebugMsg);
QDir d(downloadDir);
if (!d.removeRecursively())
reportProgress(tr("Failed to delete downloads folder %1").arg(downloadDir), QtCriticalMsg);
}
if (progress)
progress->setValue(++cnt);
if (!g.decompressDirUseDwnld() && (runParams->data.flags & UPDFLG_DelDownloads)) {
reportProgress(tr("Delete decompress directory: %1").arg(decompressDir), QtDebugMsg);
QDir d(decompressDir);
if (!d.removeRecursively())
reportProgress(tr("Failed to delete decompress folder %1").arg(decompressDir), QtCriticalMsg);
}
if (progress)
progress->setValue(++cnt);
return true;
}
bool UpdateInterface::asyncInstall()
{
return true;
}
bool UpdateInterface::saveReleaseSettings()
{
reportProgress(tr("Save release settings"), QtDebugMsg);
g.component[settingsIdx].release(releases->name());
g.component[settingsIdx].version(releases->version());
g.component[settingsIdx].id(releases->id());
g.component[settingsIdx].date(releases->date());
return true;
}
/*
UpdateFactories
*/
UpdateFactories::UpdateFactories(QWidget * parent) :
QWidget(parent)
{
registerUpdateFactories();
}
UpdateFactories::~UpdateFactories()
{
unregisterUpdateFactories();
}
void UpdateFactories::registerUpdateFactory(UpdateFactoryInterface * factory)
{
registeredUpdateFactories.append(factory);
qDebug() << "Registered update factory:" << factory->name();
}
void UpdateFactories::registerUpdateFactories()
{
// Note: register in logical updating sequence
// SDCard should be first as it may clean up/erase commmon destination directory
registerUpdateFactory(new UpdateFactory<UpdateSDCard>(this));
registerUpdateFactory(new UpdateFactory<UpdateFirmware>(this));
registerUpdateFactory(new UpdateFactory<UpdateSounds>(this));
registerUpdateFactory(new UpdateFactory<UpdateThemes>(this));
registerUpdateFactory(new UpdateFactory<UpdateMultiProtocol>(this));
// Note: Companion must be last as its install requires the app to be closed and thus would interrupt the update loop
registerUpdateFactory(new UpdateFactory<UpdateCompanion>(this));
}
void UpdateFactories::unregisterUpdateFactories()
{
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories)
delete factory;
}
const UpdateParameters * const UpdateFactories::getDefaultParams(const QString & name)
{
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
return factory->instance()->getDefaultParams();
break;
}
}
return nullptr;
}
UpdateParameters * const UpdateFactories::getRunParams(const QString & name)
{
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
return factory->instance()->getRunParams();
break;
}
}
return nullptr;
}
void UpdateFactories::resetRunEnvironment(const QString & name)
{
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
factory->instance()->resetRunEnvironment();
break;
}
}
}
void UpdateFactories::resetAllRunEnvironments()
{
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
factory->instance()->resetRunEnvironment();
}
}
void UpdateFactories::setRunUpdate(const QString & name)
{
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
factory->instance()->setRunUpdate();
break;
}
}
}
const QMap<QString, int> UpdateFactories::sortedComponentsList()
{
QMap<QString, int> map;
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
map.insert(factory->name(), factory->instance()->settingsIdx);
}
return map;
}
void UpdateFactories::clearRelease(const QString & name)
{
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
factory->instance()->clearRelease();
break;
}
}
}
const QString UpdateFactories::currentRelease(const QString & name)
{
QString ret;
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
ret = factory->instance()->currentRelease();
break;
}
}
return ret;
}
const QString UpdateFactories::updateRelease(const QString & name)
{
QString ret;
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
ret = factory->instance()->updateRelease();
break;
}
}
return ret;
}
const bool UpdateFactories::isLatestRelease(const QString & name)
{
bool ret = true;
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
ret = factory->instance()->isLatestRelease();
break;
}
}
return ret;
}
const QString UpdateFactories::latestRelease(const QString & name)
{
QString ret;
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
ret = factory->instance()->latestRelease();
break;
}
}
return ret;
}
const QStringList UpdateFactories::releases(const QString & name)
{
QStringList ret;
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (name == factory->name()) {
ret = factory->instance()->getReleases();
break;
}
}
return ret;
}
bool UpdateFactories::autoUpdate(ProgressWidget * progress)
{
bool ok = false;
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
ok = factory->instance()->autoUpdate(progress);
if (!ok)
break;
}
return ok;
}
bool UpdateFactories::manualUpdate(ProgressWidget * progress)
{
bool ok = false;
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
ok = factory->instance()->manualUpdate(progress);
if (!ok)
break;
}
return ok;
}
const bool UpdateFactories::updatesAvailable()
{
bool ret = false;
foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) {
if (factory->instance()->isUpdateAvailable()) {
ret = true;
}
}
return ret;
}