fix(cpn): sounds processing extra files and cancel update processes earlier (#5354)

Co-authored-by: elecpower <elecpower@users.noreply.github.com>
This commit is contained in:
Neil Horne
2024-08-01 22:59:22 +10:00
committed by GitHub
co-authored by elecpower
parent b2a603ed21
commit 4390e310d0
13 changed files with 443 additions and 210 deletions
+7
View File
@@ -212,3 +212,10 @@ void ProgressWidget::forceKeepOpen(bool value)
{
emit keepOpen(value);
}
void ProgressWidget::refresh()
{
ui->info->update();
ui->progressBar->update();
ui->textEdit->update();
}
+1
View File
@@ -53,6 +53,7 @@ class ProgressWidget : public QWidget
void stop();
void clearDetails() const;
void forceKeepOpen(bool value);
void refresh();
signals:
void detailsToggled();
+20 -2
View File
@@ -27,14 +27,17 @@
#include <QFileInfo>
#include <QDebug>
#include <QWidget>
#include <QApplication>
MinizInterface::MinizInterface(ProgressWidget * progress, const ProgressCalcMethod progressMethod, const int & logLevel) :
QObject(nullptr),
progress(progress),
progressMethod(progressMethod),
progressValue(0),
logLevel(logLevel),
path(""),
archiveFile("")
archiveFile(""),
stopping(false)
{
reportProgress(tr("Progress calculation method: file %1").arg(progressMethod == PCM_COUNT ? tr("count") : tr("size")), QtDebugMsg);
}
@@ -171,8 +174,10 @@ bool MinizInterface::addFileToArchive(const QString & path)
bool MinizInterface::unzipArchiveToPath(const QString & archiveFile, const QString & destinationPath)
{
QFileInfo file(archiveFile);
if (progress)
progress->setInfo(tr("Decompressing %1").arg(archiveFile));
progress->setInfo(tr("Decompressing %1").arg(file.fileName()));
else
qDebug() << QString("Decompressing %1 to %2").arg(archiveFile, destinationPath);
@@ -212,6 +217,12 @@ bool MinizInterface::unzipArchiveToPath(const QString & archiveFile, const QStri
}
for (int i = 0; i < fileCount; i++) {
if (isStopping()) {
mz_zip_reader_end(&zip_archive);
reportProgress(tr("Decompression interrupted"), QtWarningMsg);
return false;
}
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) {
mz_zip_reader_end(&zip_archive);
reportProgress(tr("Unable to obtain file status for index: %1").arg(i), QtFatalMsg);
@@ -254,6 +265,8 @@ bool MinizInterface::unzipArchiveToPath(const QString & archiveFile, const QStri
progress->setValue(progressValue);
reportProgress(tr("Extracted file: %1").arg(file_stat.m_filename), QtDebugMsg);
// to assist in responsiveness and allow stop signal to be received and processed
QApplication::processEvents();
}
}
@@ -290,3 +303,8 @@ void MinizInterface::reportProgress(const QString & text, const int & type, bool
qDebug() << text;
}
}
void MinizInterface::stop()
{
stopping = true;
}
+7 -2
View File
@@ -28,9 +28,9 @@
class ProgressWidget;
class MinizInterface
class MinizInterface : public QObject
{
Q_DECLARE_TR_FUNCTIONS(MinizInterface)
Q_OBJECT
public:
enum ProgressCalcMethod {
@@ -44,6 +44,9 @@ class MinizInterface
bool zipPathToFile(const QString & sourcePath, const QString & archiveFile, bool append = true);
bool unzipArchiveToPath(const QString & archiveFile, const QString & destinationPath);
public slots:
void stop();
private:
ProgressWidget *progress;
ProgressCalcMethod progressMethod;
@@ -51,10 +54,12 @@ class MinizInterface
int logLevel;
QString path;
QString archiveFile;
bool stopping;
mz_zip_archive zip_archive;
bool addFileToArchive(const QString & path);
bool createDirectory(const QString & path);
void reportProgress(const QString & text, const int & type = QtInfoMsg, bool richText = false);
bool isStopping() { return stopping; }
};
+14
View File
@@ -106,6 +106,15 @@ bool AssetsRawItemModel::setDownloadName(const int id, const QString name)
return setValue(id, RIMR_DownloadName, QVariant(name));
}
bool AssetsRawItemModel::resetFlags()
{
for (int i = 0; i < rowCount(); i++) {
item(i)->setData(0, RIMR_Flags);
}
return true;
}
/*
AssetsFilteredItemModel
*/
@@ -320,3 +329,8 @@ bool RepoAssets::setDownloadName(const QString name)
{
return m_rawItemModel->setDownloadName(id(), name);
}
bool RepoAssets::resetFlags()
{
return m_rawItemModel->resetFlags();
}
+7
View File
@@ -39,6 +39,7 @@ class AssetsRawItemModel : public RepoRawItemModel
bool setDownloadUrl(const int id, const QString url);
bool setDownloadName(const int id, const QString name);
bool resetFlags();
private:
QObject *m_parentRepo; // cannot use Repo class from repo.h as compile error due to cyclic headers
@@ -93,16 +94,22 @@ class RepoAssets : public QObject, public RepoMetaData
bool getJson(const QString assetName, QJsonDocument * json);
const QString name() const;
const QString name(const int row);
bool setCopyFilter(const QString filter);
bool setSubDirectory(const QString path);
const QString subDirectory() const;
bool setDownloadUrl(const QString url);
const QString downloadUrl() const;
const QString downloadUrl(const int row);
bool setDownloadName(const QString name);
const QString downloadName() const;
const QString downloadName(const int row);
bool resetFlags();
signals:
void idChanged(int id);
+59 -21
View File
@@ -25,11 +25,10 @@
#include "eeprominterface.h"
#include "flashfirmwaredialog.h"
constexpr char STATUS_UNKNOWN[] {"STATUS_UNKNOWN"};
constexpr char STATUS_WAITING_FOR_BUILD[] {"WAITING_FOR_BUILD"};
constexpr char STATUS_BUILD_IN_PROGRESS[] {"BUILD_IN_PROGRESS"};
constexpr char STATUS_BUILD_SUCCESS[] {"BUILD_SUCCESS"};
// constexpr char STATUS_BUILD_ERROR[] {"BUILD_ERROR"};
// these must match text strings in server response
constexpr char CLOUD_BUILD_WAITING[] {"WAITING_FOR_BUILD"};
constexpr char CLOUD_BUILD_IN_PROGRESS[] {"BUILD_IN_PROGRESS"};
constexpr char CLOUD_BUILD_SUCCESS[] {"BUILD_SUCCESS"};
UpdateCloudBuild::UpdateCloudBuild(QWidget * parent) :
UpdateInterface(parent, CID_CloudBuild, tr("CloudBuild"), Repo::REPO_TYPE_BUILD,
@@ -48,6 +47,38 @@ UpdateCloudBuild::~UpdateCloudBuild()
delete m_docResp;
}
QString UpdateCloudBuild::statusToString(const int status)
{
switch (status) {
case STATUS_WAITING:
return tr("waiting");
case STATUS_IN_PROGRESS:
return tr("in progress");
case STATUS_SUCCESS:
return tr("success");
case STATUS_ERROR:
return tr("error");
case STATUS_TIMEOUT:
return tr("timeout");
case STATUS_CANCELLED:
return tr("cancelled");
default:
return tr("unknown");
}
}
int UpdateCloudBuild::cloudStatusToInt(const QString status)
{
if (status == QString(CLOUD_BUILD_WAITING))
return STATUS_WAITING;
else if (status == QString(CLOUD_BUILD_IN_PROGRESS))
return STATUS_IN_PROGRESS;
else if (status == QString(CLOUD_BUILD_SUCCESS))
return STATUS_SUCCESS;
else
return STATUS_UNKNOWN;
}
void UpdateCloudBuild::assetSettingsInit()
{
if (!isSettingsIndexValid())
@@ -189,8 +220,8 @@ int UpdateCloudBuild::asyncInstall()
bool UpdateCloudBuild::buildFlaggedAsset(const int row)
{
m_jobStatus = STATUS_UNKNOWN;
m_buildFlags.clear();
m_jobStatus.clear();
m_profileOpts.clear();
m_radio = repo()->assets()->name(row);
@@ -289,7 +320,7 @@ bool UpdateCloudBuild::buildFlaggedAsset(const int row)
if (params()->logLevel == QtDebugMsg)
network()->saveJsonDocToFile(docBody, QString("%1/%2_body.txt").arg(m_logDir).arg(m_radio));
network()->submitRequest(tr("Submit firmware build"), repo()->urlJobs(), docBody, m_docResp);
network()->submitRequest(tr("Submit build request"), repo()->urlJobs(), docBody, m_docResp);
if (params()->logLevel == QtDebugMsg)
network()->saveBufferToFile(QString("%1/%2_buffer.txt").arg(m_logDir).arg(m_radio));
@@ -318,15 +349,18 @@ bool UpdateCloudBuild::buildFlaggedAsset(const int row)
return false;
}
else {
m_jobStatus = obj.value("status").toString(QString(STATUS_UNKNOWN));
m_jobStatus = cloudStatusToInt(obj.value("status").toString());
}
if (isStatusInProgress())
waitForBuildFinish();
status()->reportProgress(tr("Firmware build finished status: %1").arg(m_jobStatus));
status()->reportProgress(tr("Build finish status: %1").arg(statusToString(m_jobStatus)));
if (m_jobStatus == QString(STATUS_BUILD_SUCCESS) && setAssetDownload())
// ensure progress bar not in wait state
status()->setMaximum(100);
if (m_jobStatus == STATUS_SUCCESS && setAssetDownload())
return true;
return false;
@@ -334,22 +368,25 @@ bool UpdateCloudBuild::buildFlaggedAsset(const int row)
void UpdateCloudBuild::cancel()
{
status()->reportProgress(tr("Build firmware cancelled"), QtWarningMsg);
// this does not cancel the build job on the server but only interrupts the client side
status()->reportProgress(tr("Build cancelled"), QtWarningMsg);
m_jobStatus = STATUS_CANCELLED;
cleanup();
}
void UpdateCloudBuild::checkStatus()
{
if (m_buildStartTime.secsTo(QTime::currentTime()) > 180) {
status()->reportProgress(tr("Build firmware timeout"), QtWarningMsg);
status()->reportProgress(tr("Build timeout. Retry later."), QtWarningMsg);
m_jobStatus = STATUS_TIMEOUT;
cleanup();
return;
}
getStatus();
if (!isStatusInProgress())
cleanup();
}
void UpdateCloudBuild::cleanup()
@@ -363,7 +400,7 @@ void UpdateCloudBuild::cleanup()
bool UpdateCloudBuild::getStatus()
{
m_jobStatus = QString(STATUS_UNKNOWN);
m_jobStatus = STATUS_UNKNOWN;
QJsonDocument *docBody = new QJsonDocument(*m_objBody);
if (m_docResp)
@@ -376,9 +413,9 @@ bool UpdateCloudBuild::getStatus()
network()->saveJsonDocToFile(m_docResp, QString("%1/%2_status_%3.txt").arg(m_logDir).arg(m_radio).arg(QTime::currentTime().toString("hhmmss")));
if (m_docResp->isObject())
m_jobStatus = m_docResp->object().value("status").toString(QString(STATUS_UNKNOWN));
m_jobStatus = cloudStatusToInt(m_docResp->object().value("status").toString());
if (m_jobStatus == QString(STATUS_UNKNOWN)) {
if (m_jobStatus == STATUS_UNKNOWN) {
status()->reportProgress(tr("Build status unknown"), QtCriticalMsg);
return false;
}
@@ -388,7 +425,7 @@ bool UpdateCloudBuild::getStatus()
bool UpdateCloudBuild::isStatusInProgress()
{
if (m_jobStatus == QString(STATUS_WAITING_FOR_BUILD) || m_jobStatus == QString(STATUS_BUILD_IN_PROGRESS))
if (m_jobStatus == STATUS_WAITING || m_jobStatus == STATUS_IN_PROGRESS)
return true;
return false;
@@ -451,15 +488,16 @@ bool UpdateCloudBuild::stringExists(const QJsonObject & parent, const QString ch
void UpdateCloudBuild::waitForBuildFinish()
{
status()->progressMessage(tr("Waiting for firmware build to finish..."));
status()->progressMessage(tr("Waiting for build to finish..."));
status()->setMaximum(0); // sets progress bar to wait state
m_timer.setInterval(2000); // check status every 2 seconds
connect(this, &UpdateInterface::stop, this, &UpdateCloudBuild::cancel);
connect(this, &UpdateInterface::stopping, this, &UpdateCloudBuild::cancel);
connect(&m_timer, &QTimer::timeout, this, &UpdateCloudBuild::checkStatus);
m_timer.start();
m_eventLoop.exec();
disconnect(this, &UpdateInterface::stop, this, &UpdateCloudBuild::cancel);
disconnect(&m_timer, nullptr);
disconnect(&m_timer, &QTimer::timeout, this, &UpdateCloudBuild::checkStatus);
disconnect(this, &UpdateInterface::stopping, this, &UpdateCloudBuild::cancel);
}
+13 -1
View File
@@ -31,6 +31,16 @@ class UpdateCloudBuild: public UpdateInterface
Q_DECLARE_TR_FUNCTIONS(UpdateCloudBuild)
public:
enum ProcessStatus {
STATUS_UNKNOWN,
STATUS_WAITING,
STATUS_IN_PROGRESS,
STATUS_SUCCESS,
STATUS_ERROR,
STATUS_TIMEOUT,
STATUS_CANCELLED
};
Q_ENUM(ProcessStatus)
explicit UpdateCloudBuild(QWidget * parent);
virtual ~UpdateCloudBuild();
@@ -53,7 +63,7 @@ class UpdateCloudBuild: public UpdateInterface
QString m_logDir;
QString m_buildFlags;
QTime m_buildStartTime;
QString m_jobStatus;
int m_jobStatus;
QStringList m_profileOpts;
QString m_radio;
@@ -69,4 +79,6 @@ class UpdateCloudBuild: public UpdateInterface
bool isStatusInProgress();
bool setAssetDownload();
void waitForBuildFinish();
static QString statusToString(const int status);
static int cloudStatusToInt(const QString status);
};
+162 -65
View File
@@ -130,11 +130,16 @@ int UpdateInterface::build()
bool UpdateInterface::buildFlaggedAssets()
{
if (!isOkay())
return true;
m_repo->assets()->setFilterFlags(UPDFLG_Build);
m_status->reportProgress(tr("Asset filter applied: %1 - %2 found").arg(updateFlagToString(UPDFLG_Build)).arg(m_repo->assets()->count()), QtDebugMsg);
for (int i = 0; i < m_repo->assets()->count(); ++i) {
if (!buildFlaggedAsset(i))
if (!isOkay())
return true;
else if (!buildFlaggedAsset(i))
return false;
}
@@ -211,9 +216,11 @@ bool UpdateInterface::copyFiles()
int cnt = 0;
processEvents();
//m_status->reportProgress(tr("Calculating number of files"), QtDebugMsg);
while (itcnt.hasNext())
while (isOkay() && itcnt.hasNext())
{
QFileInfo src(itcnt.next());
@@ -229,7 +236,9 @@ bool UpdateInterface::copyFiles()
cnt = 0;
while (itfiles.hasNext())
processEvents();
while (isOkay() && itfiles.hasNext())
{
QFileInfo src(itfiles.next());
@@ -254,6 +263,7 @@ bool UpdateInterface::copyFiles()
m_status->reportProgress(tr("Copied %1 to %2").arg(src.fileName()).arg(dest.fileName()), QtDebugMsg);
cnt++;
m_status->setValue(cnt);
processEvents();
}
if (!cnt) {
@@ -264,17 +274,23 @@ bool UpdateInterface::copyFiles()
m_status->setValue(m_status->maximum());
m_status->reportProgress(tr("Files copied: %1").arg(cnt), QtInfoMsg);
if (!isOkay())
return false;
return true;
}
bool UpdateInterface::copyFlaggedAssets()
{
if (!isOkay())
return false;
m_repo->assets()->setFilterFlags(UPDFLG_CopyDest);
m_status->reportProgress(tr("Asset filter applied: %1 - %2 found").arg(updateFlagToString(UPDFLG_CopyDest)).arg(m_repo->assets()->count()), QtDebugMsg);
for (int i = 0; i < m_repo->assets()->count(); ++i) {
m_repo->assets()->getSetId(i);
if (!copyAsset())
if ( !isOkay() || !copyAsset())
return false;
}
@@ -307,7 +323,9 @@ bool UpdateInterface::copyStructure()
int cnt = 0;
while (itdirs.hasNext())
processEvents();
while (isOkay() && itdirs.hasNext())
{
QFileInfo src(itdirs.next());
@@ -323,6 +341,7 @@ bool UpdateInterface::copyStructure()
m_status->reportProgress(tr("Check/create directory: %1").arg(dir), QtDebugMsg);
cnt++;
processEvents();
}
m_status->reportProgress(tr("Directories checked/created: %1").arg(cnt), QtInfoMsg);
@@ -334,9 +353,11 @@ bool UpdateInterface::copyStructure()
cnt = 0;
processEvents();
//m_status->reportProgress(tr("Calculating number of files"), QtDebugMsg);
while (itcnt.hasNext())
while (isOkay() && itcnt.hasNext())
{
QFileInfo src(itcnt.next());
@@ -352,7 +373,9 @@ bool UpdateInterface::copyStructure()
cnt = 0;
while (itfiles.hasNext())
processEvents();
while (isOkay() && itfiles.hasNext())
{
QFileInfo src(itfiles.next());
@@ -379,20 +402,24 @@ bool UpdateInterface::copyStructure()
m_status->reportProgress(tr("Copied %1 to %2").arg(src.fileName()).arg(dest.fileName()), QtDebugMsg);
cnt++;
m_status->setValue(cnt);
processEvents();
}
m_status->setValue(m_status->maximum());
m_status->reportProgress(tr("Files copied: %1").arg(cnt), QtInfoMsg);
if (!isOkay())
return false;
return true;
}
int UpdateInterface::copyToDestination()
{
m_status->progressMessage(tr("Copying to destination"));
m_status->progressMessage(tr("Copying files to destination"));
if (!copyFlaggedAssets()) {
m_status->reportProgress(tr("Unable to copy assets"), QtDebugMsg);
m_status->reportProgress(tr("Unable to copy files"), QtDebugMsg);
return false;
}
@@ -401,10 +428,10 @@ int UpdateInterface::copyToDestination()
int UpdateInterface::decompress()
{
m_status->progressMessage(tr("Decompressing assets"));
m_status->progressMessage(tr("Decompressing files"));
if (!decompressFlaggedAssets()) {
m_status->reportProgress(tr("Unable to decompress flagged assets"), QtDebugMsg);
m_status->reportProgress(tr("Unable to decompress flagged files"), QtDebugMsg);
return false;
}
@@ -418,11 +445,16 @@ const QString UpdateInterface::decompressDir() const
bool UpdateInterface::decompressFlaggedAssets()
{
if (!isOkay())
return true;
m_repo->assets()->setFilterFlags(UPDFLG_Decompress);
m_status->reportProgress(tr("Asset filter applied: %1 - %2 found").arg(updateFlagToString(UPDFLG_Decompress)).arg(m_repo->assets()->count()), QtDebugMsg);
for (int i = 0; i < m_repo->assets()->count(); ++i) {
if (!decompressAsset(i))
if (!isOkay())
return true;
else if (!decompressAsset(i))
return false;
}
@@ -454,6 +486,8 @@ bool UpdateInterface::decompressAsset(int row)
bool UpdateInterface::decompressArchive(const QString & archivePath, const QString & destDir)
{
bool ret = true;
QString dest = destDir;
if (!dest.endsWith("/"))
@@ -463,12 +497,16 @@ bool UpdateInterface::decompressArchive(const QString & archivePath, const QStri
MinizInterface mIface(m_status->progress(), MinizInterface::PCM_SIZE, m_params->logLevel);
connect(this, &UpdateInterface::stopping, &mIface, &MinizInterface::stop, Qt::DirectConnection);
if (!mIface.unzipArchiveToPath(archivePath, dest)) {
m_status->reportProgress(tr("Failed to decompress %1").arg(archivePath), QtCriticalMsg);
return false;
ret = false;
}
return true;
disconnect(this, &UpdateInterface::stopping, &mIface, &MinizInterface::stop);
return ret;
}
const QString UpdateInterface::downloadDir() const
@@ -478,11 +516,16 @@ const QString UpdateInterface::downloadDir() const
bool UpdateInterface::downloadFlaggedAssets()
{
if (!isOkay())
return true;
m_repo->assets()->setFilterFlags(UPDFLG_Download);
m_status->reportProgress(tr("Asset filter applied: %1 - %2 found").arg(updateFlagToString(UPDFLG_Download)).arg(m_repo->assets()->count()), QtDebugMsg);
for (int i = 0; i < m_repo->assets()->count(); ++i) {
if (!downloadFlaggedAsset(i))
if (!isOkay())
return true;
else if (!downloadFlaggedAsset(i))
return false;
}
@@ -499,7 +542,7 @@ int UpdateInterface::download()
m_status->progressMessage(tr("Downloading..."));
if (!downloadFlaggedAssets()) {
m_status->reportProgress(tr("Unable to download flagged assets"), QtDebugMsg);
m_status->reportProgress(tr("Unable to download flagged files"), QtDebugMsg);
return false;
}
@@ -540,6 +583,9 @@ bool UpdateInterface::flagAssets()
int UpdateInterface::housekeeping()
{
if (!isOkay())
return PROC_RESULT_CANCELLED;
m_status->progressMessage(tr("Housekeeping"));
int cnt = 0;
@@ -667,10 +713,16 @@ UpdateNetwork* const UpdateInterface::network() const
void UpdateInterface::onStatusCancelled()
{
m_status->setMaximum(0);
m_result = PROC_RESULT_CANCELLED;
m_stopping = true;
emit stop();
m_status->reportProgress(tr("Update cancelled"), QtWarningMsg);
m_status->setValue(100);
m_status->reportProgress(tr("Update cancelled by user"), QtWarningMsg);
emit stopping();
if (m_eventLoop.isRunning()) {
m_eventLoop.processEvents();
m_eventLoop.quit();
}
}
UpdateParameters* const UpdateInterface::params() const
@@ -709,6 +761,10 @@ int UpdateInterface::preparation()
if (!m_repo->assets()->retrieveMetaDataAll())
return false;
// clear filters from previous runs
if (!m_repo->assets()->resetFlags())
return false;
m_status->setValue(++cnt);
if (!flagAssets())
@@ -719,6 +775,11 @@ int UpdateInterface::preparation()
return true;
}
void UpdateInterface::processEvents()
{
m_eventLoop.processEvents();
}
void UpdateInterface::radioProfileChanged()
{
resetEnvironment();
@@ -809,57 +870,73 @@ bool UpdateInterface::retrieveRepoJsonFile(const QString & filename, QJsonDocume
void UpdateInterface::runAsyncInstall()
{
m_status->setValue(0);
m_result = asyncInstall();
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 start async failed").arg(m_name), QtCriticalMsg);
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 start async %2").arg(m_name).arg(resultToString()), QtCriticalMsg);
m_status->setValue(m_status->maximum());
emit finished();
}
void UpdateInterface::runBuild()
{
m_status->setValue(0);
m_result = build();
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 preparation failed").arg(m_name), QtCriticalMsg);
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 preparation %2").arg(m_name).arg(resultToString()), QtCriticalMsg);
m_status->setValue(m_status->maximum());
emit finished();
}
void UpdateInterface::runCopyToDestination()
{
m_status->setValue(0);
m_result = copyToDestination();
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 copy to destination failed").arg(m_name), QtCriticalMsg);
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 copy to destination %2").arg(m_name).arg(resultToString()), QtCriticalMsg);
m_status->setValue(m_status->maximum());
emit finished();
}
void UpdateInterface::runDecompress()
{
m_status->setValue(0);
m_result = decompress();
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 decompress failed").arg(m_name), QtCriticalMsg);
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 decompress %2").arg(m_name).arg(resultToString()), QtCriticalMsg);
m_status->setValue(m_status->maximum());
emit finished();
}
void UpdateInterface::runDownload()
{
m_status->setValue(0);
m_result = download();
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 download failed").arg(m_name), QtCriticalMsg);
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 download %2").arg(m_name).arg(resultToString()), QtCriticalMsg);
m_status->setValue(m_status->maximum());
emit finished();
}
void UpdateInterface::runHousekeeping()
{
m_status->setValue(0);
m_result = housekeeping();
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 housekeeping failed").arg(m_name), QtCriticalMsg);
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 housekeeping %2").arg(m_name).arg(resultToString()), QtCriticalMsg);
m_status->setValue(m_status->maximum());
emit finished();
}
void UpdateInterface::runPreparation()
{
m_status->setValue(0);
m_result = preparation();
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 preparation failed").arg(m_name), QtCriticalMsg);
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 preparation %2").arg(m_name).arg(resultToString()), QtCriticalMsg);
m_status->setValue(m_status->maximum());
emit finished();
}
void UpdateInterface::runReleaseSettingsSave()
{
m_status->setValue(0);
m_result = releaseSettingsSave();
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 save release settings failed").arg(m_name), QtCriticalMsg);
if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 save release settings %2").arg(m_name).arg(resultToString()), QtCriticalMsg);
m_status->setValue(m_status->maximum());
emit finished();
}
@@ -1021,72 +1098,92 @@ bool UpdateInterface::update(ProgressWidget * progress)
// and then display close button
status()->keepOpen(true);
// handle Cancel button pressed
connect(m_status, &UpdateStatus::cancelled, this, &UpdateInterface::onStatusCancelled);
// need to create a separate thread for the processes so that the cancel request can be handled asap
QEventLoop loop;
//=====================================================================================================
// EXTREMELY IMPORTANT !!!!!!
//
// Every process within the event loop MUST emit finished() otherwise the event loop will not exit
//=====================================================================================================
connect(this, &UpdateInterface::finished, [&]() {
if (m_eventLoop.isRunning())
m_eventLoop.quit();
});
// EXTREMELY IMPORTANT: every process within the event loop MUST emit finished() otherwise the loop will not exit
// Note: finished does not indicate the exit status
connect(this, &UpdateInterface::finished, [&]() { if (loop.isRunning()) loop.quit(); });
if (okToRun()) {
// sub-processes should also perform this test to return as early as possible on receiving cancel request
if (isOkay()) {
// pause before starting next process to allow time for queued events to be processed ie like the cancel request
QTimer::singleShot(100, this, &UpdateInterface::runPreparation);
// NOTE: this will not exit until the finished signal is detected and processed
loop.exec();
QTimer::singleShot(500, this, &UpdateInterface::runPreparation);
// NOTE: this will not exit until the finished or cancelled signal is detected and processed
m_eventLoop.exec();
}
if (okToRun()) {
QTimer::singleShot(100, this, &UpdateInterface::runBuild);
loop.exec();
if (isOkay()) {
QTimer::singleShot(500, this, &UpdateInterface::runBuild);
m_eventLoop.exec();
}
if (okToRun()) {
QTimer::singleShot(100, this, &UpdateInterface::runDownload);
loop.exec();
if (isOkay()) {
QTimer::singleShot(500, this, &UpdateInterface::runDownload);
m_eventLoop.exec();
}
if (okToRun()) {
QTimer::singleShot(100, this, &UpdateInterface::runDecompress);
loop.exec();
if (isOkay()) {
QTimer::singleShot(500, this, &UpdateInterface::runDecompress);
m_eventLoop.exec();
}
if (okToRun()) {
QTimer::singleShot(100, this, &UpdateInterface::runCopyToDestination);
loop.exec();
if (isOkay()) {
QTimer::singleShot(500, this, &UpdateInterface::runCopyToDestination);
m_eventLoop.exec();
}
// perform before async install in case Companion restarted
if (okToRun()) {
QTimer::singleShot(100, this, &UpdateInterface::runReleaseSettingsSave);
loop.exec();
if (isOkay()) {
QTimer::singleShot(500, this, &UpdateInterface::runReleaseSettingsSave);
m_eventLoop.exec();
}
if (okToRun()) {
QTimer::singleShot(100, this, &UpdateInterface::runAsyncInstall);
loop.exec();
if (isOkay()) {
QTimer::singleShot(500, this, &UpdateInterface::runAsyncInstall);
m_eventLoop.exec();
}
if (okToRun()) {
QTimer::singleShot(100, this, &UpdateInterface::runHousekeeping);
loop.exec();
if (isOkay()) {
QTimer::singleShot(500, this, &UpdateInterface::runHousekeeping);
m_eventLoop.exec();
}
// remove event check from stack otherwise repeated running of update will add unnecessary duplicates
disconnect(m_status, &UpdateStatus::cancelled, this, &UpdateInterface::onStatusCancelled);
m_status->reportProgress(tr("%1 update %2").arg(m_name).arg(resultToString()), QtInfoMsg);
if (!m_status->progress())
QMessageBox::information(m_status->progress(), CPN_STR_APP_NAME, tr("%1 update %2").arg(m_name).arg(resultToString()));
// allow the dialog to be closed by clicking the close button
status()->keepOpen(false);
if (!okToRun())
return false;
if (m_result != PROC_RESULT_SUCCESS)
m_status->setValue(m_status->maximum());
m_status->reportProgress(tr("%1 update successful").arg(m_name), QtInfoMsg);
return m_result == PROC_RESULT_SUCCESS;
}
if (!m_status->progress())
QMessageBox::information(m_status->progress(), CPN_STR_APP_NAME, tr("%1 update successful").arg(m_name));
return true;
const QString UpdateInterface::resultToString() const
{
switch (m_result) {
case PROC_RESULT_FAIL:
return tr("failed");
case PROC_RESULT_SUCCESS:
return tr("successful");
case PROC_RESULT_CANCELLED:
return tr("cancelled");
default:
return tr("unknown");
}
}
const QString UpdateInterface::updateDir() const
+7 -2
View File
@@ -107,7 +107,7 @@ class UpdateInterface : public QWidget
bool update(ProgressWidget * progress = nullptr);
signals:
void stop();
void stopping();
void finished();
protected:
@@ -156,6 +156,7 @@ class UpdateInterface : public QWidget
private slots:
void onStatusCancelled();
void processEvents();
private:
const ComponentIdentity m_id;
@@ -169,6 +170,8 @@ class UpdateInterface : public QWidget
QString m_updateDir;
bool m_stopping;
int m_result;
QEventLoop m_eventLoop;
QTimer m_timer;
void appSettingsInit();
bool checkCreateDirectory(const QString & dirSetting, const UpdateFlags flag);
@@ -177,7 +180,9 @@ class UpdateInterface : public QWidget
bool setRunFolders();
bool validateFolder(QString & fldr);
bool okToRun() { return m_result == PROC_RESULT_SUCCESS && m_stopping == false; }
bool isOkay() { return m_result == PROC_RESULT_SUCCESS && !m_stopping; }
const QString resultToString() const;
void runAsyncInstall();
void runBuild();
void runCopyToDestination();
+133 -116
View File
@@ -49,6 +49,30 @@ UpdateNetwork::~UpdateNetwork()
delete m_file;
}
void UpdateNetwork::cleanup()
{
if (m_timer.isActive())
m_timer.stop();
disconnect(&m_timer, &QTimer::timeout, this, &UpdateNetwork::updateProgress);
if (m_eventLoop.isRunning())
m_eventLoop.quit();
}
void UpdateNetwork::connectReplyCommon()
{
connect(m_reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::errorOccurred), [=] (QNetworkReply::NetworkError code) {
m_success = false;
m_status->reportProgress(tr("Network error has occurred. Error code: %1").arg(code), QtCriticalMsg);
});
connect(m_reply, &QNetworkReply::sslErrors, [=]() {
m_success = false;
m_status->reportProgress(tr("Ssl library version: %1").arg(QSslSocket::sslLibraryVersionString()), QtCriticalMsg);
});
}
void UpdateNetwork::convertBufferToJson(QJsonDocument * json)
{
m_success = false;
@@ -64,6 +88,57 @@ void UpdateNetwork::convertBufferToJson(QJsonDocument * json)
m_success = true;
}
void UpdateNetwork::download(const DownloadDataType type, const QString & url, const char * acceptHeader, const QString & filePath)
{
m_action = tr("Downloading");
if (!init(url))
return;
if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) {
m_file = new QFile(filePath);
if (!m_file->open(QIODevice::WriteOnly)) {
m_status->reportProgress(tr("Unable to open the download file %1 for writing. Error: %2").arg(filePath).arg(m_file->errorString()), QtCriticalMsg);
return;
}
}
else
m_file = nullptr;
if (type >= DDT_GitHub_First && type <= DDT_GitHub_Last) {
m_request.setRawHeader(QByteArray("X-GitHub-Api-Version"), GH_API_VERSION);
m_request.setRawHeader(QByteArray("Accept"), QByteArray(acceptHeader));
}
// returns a new QNetworkReply object
m_reply = m_manager.get(m_request);
// connect to the signals of the new object
connectReplyCommon();
connect(m_reply, &QNetworkReply::downloadProgress, [=](const qint64 bytesRead, const qint64 totalBytes) {
m_status->setMaximum(totalBytes);
m_status->setValue(bytesRead);
});
connect(m_reply, &QNetworkReply::readyRead, [=]() {
if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) {
m_file->write(m_reply->readAll());
}
else {
const QByteArray qba = m_reply->readAll();
m_buffer->append(qba);
}
});
connect(m_reply, &QNetworkReply::finished, [=]() { onGetFinished(m_reply, type); });
connect(m_status, &UpdateStatus::cancelled, this, &UpdateNetwork::cancelDownload);
m_timer.start();
m_eventLoop.exec();
}
// static
QString UpdateNetwork::downloadDataTypeToString(const DownloadDataType val)
{
@@ -140,6 +215,7 @@ void UpdateNetwork::downloadToFile(const DownloadDataType type, const QString &
int reply = QMessageBox::question(m_status->progress(), CPN_STR_APP_NAME, tr("File %1 exists. Download again?").arg(fi.fileName()),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No);
if (reply == QMessageBox::Cancel) {
m_status->reportProgress(tr("Download cancelled by user"), QtWarningMsg);
return;
}
else if (reply == QMessageBox::No) {
@@ -162,82 +238,6 @@ void UpdateNetwork::downloadToFile(const DownloadDataType type, const QString &
fi.absoluteFilePath());
}
void UpdateNetwork::download(const DownloadDataType type, const QString & url, const char * acceptHeader, const QString & filePath)
{
m_status->setValue(0);
m_status->setMaximum(100);
if (!init(url))
return;
if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) {
m_file = new QFile(filePath);
if (!m_file->open(QIODevice::WriteOnly)) {
m_status->reportProgress(tr("Unable to open the download file %1 for writing. Error: %2").arg(filePath).arg(m_file->errorString()), QtCriticalMsg);
return;
}
}
else
m_file = nullptr;
if (type >= DDT_GitHub_First && type <= DDT_GitHub_Last) {
m_request.setRawHeader(QByteArray("X-GitHub-Api-Version"), GH_API_VERSION);
m_request.setRawHeader(QByteArray("Accept"), QByteArray(acceptHeader));
}
// run GET inside event loop with timer interrupts to allow other events to run
QEventLoop loop;
QTimer timer;
timer.setInterval(1000);
connect(&timer, &QTimer::timeout, [&]() {
m_status->reportProgress(tr("Downloading..."), QtDebugMsg);
});
m_reply = m_manager.get(m_request);
connect(m_reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::errorOccurred), [=] (QNetworkReply::NetworkError code) {
m_success = false;
m_status->reportProgress(tr("Network error has occurred. Error code: %1").arg(code), QtCriticalMsg);
});
connect(m_reply, &QNetworkReply::sslErrors, [=]() {
m_success = false;
m_status->reportProgress(tr("Ssl library version: %1").arg(QSslSocket::sslLibraryVersionString()), QtCriticalMsg);
});
connect(m_reply, &QNetworkReply::downloadProgress, [=](const qint64 bytesRead, const qint64 totalBytes) {
m_status->setMaximum(totalBytes);
m_status->setValue(bytesRead);
});
connect(m_reply, &QNetworkReply::readyRead, [=]() {
if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) {
m_file->write(m_reply->readAll());
}
else {
const QByteArray qba = m_reply->readAll();
m_buffer->append(qba);
}
});
connect(m_reply, &QNetworkReply::finished, [&]() {
if (timer.isActive())
timer.stop();
if (loop.isRunning())
loop.quit();
disconnect(m_reply, nullptr);
onDownloadFinished(m_reply, type);
});
timer.start();
loop.exec();
}
QByteArray * UpdateNetwork::getDownloadBuffer()
{
return m_buffer;
@@ -245,10 +245,21 @@ QByteArray * UpdateNetwork::getDownloadBuffer()
bool UpdateNetwork::init(const QString & urlStr)
{
m_status->setValue(0);
m_status->setMaximum(100);
m_buffer->clear();
m_success = false;
m_request = QNetworkRequest();
if (m_reply) {
delete m_reply;
m_reply = nullptr;
}
m_timer.setInterval(1000);
connect(&m_timer, &QTimer::timeout, this, &UpdateNetwork::updateProgress);
m_url.setUrl(urlStr);
if (!m_url.isValid()) {
@@ -269,8 +280,11 @@ const bool UpdateNetwork::isSuccess()
return m_success;
}
void UpdateNetwork::onDownloadFinished(QNetworkReply * reply, DownloadDataType type)
void UpdateNetwork::onGetFinished(QNetworkReply * reply, DownloadDataType type)
{
cleanup();
disconnect(m_status, &UpdateStatus::cancelled, this, &UpdateNetwork::cancelDownload);
m_status->setValue(m_status->maximum());
if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) {
@@ -292,64 +306,56 @@ void UpdateNetwork::onDownloadFinished(QNetworkReply * reply, DownloadDataType t
delete m_file;
m_file = nullptr;
}
if (m_reply) {
delete m_reply;
m_reply = nullptr;
}
}
void UpdateNetwork::onPostFinished()
{
cleanup();
if (m_reply->error()) {
m_success = false;
m_status->reportProgress(tr("POST error: %2\n%3").arg(m_reply->error()).arg(m_reply->errorString()), QtCriticalMsg);
}
if (m_reply) {
delete m_reply;
m_reply = nullptr;
}
m_success = true;
}
void UpdateNetwork::post(const QString & action, const QString & url, QJsonDocument * data)
{
m_status->setValue(0);
m_status->setMaximum(0); // put progress bar in wait mode
m_action = action;
if (!init(url))
return;
m_status->setMaximum(0); // put progress bar into wait mode as cannot guess how long request will run
m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
// run POST inside event loop with timer interrupts to allow other events to run
QEventLoop loop;
QTimer timer;
timer.setInterval(1000);
connect(&timer, &QTimer::timeout, [&]() {
m_status->reportProgress(QString("%1...").arg(action), QtDebugMsg);
});
// returns a new QNetworkReply object
m_reply = m_manager.post(m_request, data->toJson(QJsonDocument::Compact));
connect(m_reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::errorOccurred), [=] (QNetworkReply::NetworkError code) {
m_success = false;
m_status->reportProgress(tr("Network error has occurred. Error code: %1").arg(code), QtCriticalMsg);
});
connect(m_reply, &QNetworkReply::sslErrors, [=]() {
m_success = false;
m_status->reportProgress(tr("Ssl library version: %1").arg(QSslSocket::sslLibraryVersionString()), QtCriticalMsg);
});
// connect to the signals of the new object
connectReplyCommon();
connect(m_reply, &QNetworkReply::readyRead, [=]() {
const QByteArray qba = m_reply->readAll();
m_buffer->append(qba);
});
connect(m_reply, &QNetworkReply::finished, [&]() {
if (timer.isActive())
timer.stop();
connect(m_reply, &QNetworkReply::finished, this, &UpdateNetwork::onPostFinished);
if (loop.isRunning())
loop.quit();
disconnect(m_reply, nullptr);
if (m_reply->error()) {
m_success = false;
m_status->reportProgress(tr("POST error: %2\n%3").arg(m_reply->error()).arg(m_reply->errorString()), QtCriticalMsg);
}
m_success = true;
});
timer.start();
loop.exec();
m_timer.start();
m_eventLoop.exec();
}
bool UpdateNetwork::saveBufferToFile(const QString & filePath)
@@ -396,3 +402,14 @@ void UpdateNetwork::submitRequest(const QString & action, const QString & url, Q
if (m_success)
convertBufferToJson(response);
}
void UpdateNetwork::cancelDownload()
{
m_status->reportProgress(tr("Download cancelled"), QtWarningMsg);
m_reply->abort();
}
void UpdateNetwork::updateProgress()
{
m_status->reportProgress(QString("%1...").arg(m_action), QtDebugMsg);
}
+9 -1
View File
@@ -72,7 +72,10 @@ class UpdateNetwork : public QObject
static QString downloadDataTypeToString(const DownloadDataType val);
private slots:
void onDownloadFinished(QNetworkReply * reply, DownloadDataType ddt);
void onGetFinished(QNetworkReply * reply, DownloadDataType ddt);
void onPostFinished();
void updateProgress();
void cancelDownload();
private:
UpdateStatus *m_status;
@@ -83,6 +86,11 @@ class UpdateNetwork : public QObject
QFile *m_file;
QUrl m_url;
bool m_success;
QEventLoop m_eventLoop;
QTimer m_timer;
QString m_action;
bool init(const QString & url);
void connectReplyCommon();
void cleanup();
};
+4
View File
@@ -28,6 +28,7 @@
#include "helpers.h"
#include <QMessageBox>
#include <QApplication>
Updates::Updates(QWidget * parent, UpdateFactories * updateFactories) :
QWidget(parent),
@@ -127,6 +128,9 @@ void Updates::runUpdate()
ok = factories->updateAll(progressDialog.progress());
progressDialog.setProcessStopped();
progressDialog.progress()->setInfo(tr("Finished %1").arg(ok ? tr("successfully") : tr("with errors")));
progressDialog.progress()->setValue(progressDialog.progress()->maximum());
progressDialog.progress()->refresh();
QApplication::processEvents();
progressDialog.exec();
if (ok)