diff --git a/companion/src/apppreferencesdialog.cpp b/companion/src/apppreferencesdialog.cpp index 85e55312068..43e16291470 100644 --- a/companion/src/apppreferencesdialog.cpp +++ b/companion/src/apppreferencesdialog.cpp @@ -157,7 +157,6 @@ void AppPreferencesDialog::accept() profile.externalModuleSize(ui->externalModuleSizeCB->currentData().toInt()); profile.channelOrder(ui->channelorderCB->currentIndex()); profile.defaultMode(ui->stickmodeCB->currentIndex()); - profile.renameFwFiles(ui->renameFirmware->isChecked()); profile.burnFirmware(ui->burnFirmware->isChecked()); profile.sdPath(ui->sdPath->text()); profile.pBackupDir(ui->profilebackupPath->text()); @@ -307,7 +306,6 @@ void AppPreferencesDialog::initSettings() ui->externalModuleSizeCB->setCurrentIndex(ui->externalModuleSizeCB->findData(profile.externalModuleSize())); ui->channelorderCB->setCurrentIndex(profile.channelOrder()); ui->stickmodeCB->setCurrentIndex(profile.defaultMode()); - ui->renameFirmware->setChecked(profile.renameFwFiles()); ui->sdPath->setText(profile.sdPath()); if (!profile.pBackupDir().isEmpty()) { if (QDir(profile.pBackupDir()).exists()) { diff --git a/companion/src/apppreferencesdialog.ui b/companion/src/apppreferencesdialog.ui index 8813d7473d5..cce9a37394a 100644 --- a/companion/src/apppreferencesdialog.ui +++ b/companion/src/apppreferencesdialog.ui @@ -45,66 +45,50 @@ - 1 + 0 Radio Profile - - + + + + true + - + 0 0 - Options - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + General Settings Label - - + + + + + - + 0 0 - - - 75 - true - - - Radio Type + Options - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - true - - - - - - false + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - + + 0 @@ -116,197 +100,41 @@ - - + + 0 0 - - <html><head/><body><p>Channel order</p><p><br/></p><p>Defines the order of the default mixes created on a new model.</p></body></html> - - - 0 - - - - R E T A - - - - - R E A T - - - - - R T E A - - - - - R T A E - - - - - R A E T - - - - - R A T E - - - - - E R T A - - - - - E R A T - - - - - E T R A - - - - - E T A R - - - - - E A R T - - - - - E A T R - - - - - T R E A - - - - - T R A E - - - - - T E R A - - - - - T E A R - - - - - T A R E - - - - - T A E R - - - - - A R E T - - - - - A R T E - - - - - A E R T - - - - - A E T R - - - - - A T R E - - - - - A T E R - - - - - - - - Default Int. Module - - - - - - Select Folder + Prompt to write firmware to radio after update - - - - - 0 - 0 - - - - SD Structure path + + + + If set it will override the application general setting - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + false - - - - true - + + 0 0 - - General Settings Label - - - - - - - Prompt to run SD Sync after update - - - + + 0 @@ -314,24 +142,38 @@ - Default Channel Order - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + General Settings - - - - - + + - + 0 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 5 + + + 4 + + @@ -458,32 +300,227 @@ - - + + + + true + + + + + + + + + + External Module + + + + + + + Prompt to run SD Sync after update + + + + + + + Default Int. Module + + + + + 0 0 + + + 75 + true + + - General Settings + Radio Type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Other Settings + + + + + + + Qt::Vertical + + + + 20 + 5 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Channel order</p><p><br/></p><p>Defines the order of the default mixes created on a new model.</p></body></html> + + 0 + + + + R E T A + + + + + R E A T + + + + + R T E A + + + + + R T A E + + + + + R A E T + + + + + R A T E + + + + + E R T A + + + + + E R A T + + + + + E T R A + + + + + E T A R + + + + + E A R T + + + + + E A T R + + + + + T R E A + + + + + T R A E + + + + + T E R A + + + + + T E A R + + + + + T A R E + + + + + T A E R + + + + + A R E T + + + + + A R T E + + + + + A E R T + + + + + A E T R + + + + + A T R E + + + + + A T E R + + - - - - Qt::Vertical - - - - 20 - 5 - - - - @@ -538,46 +575,19 @@ Mode 4: - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 5 - - - 4 - - - + + - - - - - 0 - 0 - + + + + true - Default Stick Mode + + + + false @@ -613,129 +623,106 @@ Mode 4: - - + + - + 0 0 - - Qt::Horizontal + + Default Stick Mode - - + + + + Select Folder + + + + + - + 0 0 - - - 75 - true - - - Other Settings + SD Structure path + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + - + 0 0 + + The profile specific folder, if set, will override general Backup folder + - Prompt to write firmware to radio after update + Backup folder - - + + - + 0 0 - - - 75 - true - - - Profile Name + Default Channel Order - - - - - - true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - + + 0 0 - - Append version number to firmware file name - - - - - - - If set it will override the application general setting - - - false + + Qt::Horizontal - - + + - + 0 0 - - The profile specific folder, if set, will override general Backup folder - - - Backup folder + + + 75 + true + - - - - - External Module + Profile Name - - - @@ -1642,7 +1629,6 @@ Mode 4: pbackupEnable stickmodeCB channelorderCB - renameFirmware burnFirmware splashincludeCB snapshotPath diff --git a/companion/src/helpers.cpp b/companion/src/helpers.cpp index 772bb1134e1..2b8fa1d9afe 100644 --- a/companion/src/helpers.cpp +++ b/companion/src/helpers.cpp @@ -898,6 +898,20 @@ bool SemanticVersion::isEmpty() return false; } +bool SemanticVersion::isPreRelease(const QString vers) +{ + fromString(vers); + return isPreRelease(); +} + +bool SemanticVersion::isPreRelease() +{ + if (version.preReleaseType != PR_NONE) + return true; + else + return false; +} + int SemanticVersion::compare(const SemanticVersion& other) { if (version.major != other.version.major) { diff --git a/companion/src/helpers.h b/companion/src/helpers.h index be9ca4c912b..89633a7d9d5 100644 --- a/companion/src/helpers.h +++ b/companion/src/helpers.h @@ -263,6 +263,8 @@ class SemanticVersion bool fromInt(const unsigned int val); bool isEmpty(const QString vers); bool isEmpty(); + bool isPreRelease(const QString vers); + bool isPreRelease(); SemanticVersion& operator=(const SemanticVersion& rhs); diff --git a/companion/src/mainwindow.cpp b/companion/src/mainwindow.cpp index 75421cce58f..c9d552cda48 100644 --- a/companion/src/mainwindow.cpp +++ b/companion/src/mainwindow.cpp @@ -1250,6 +1250,7 @@ void MainWindow::onCurrentProfileChanged() g.moveCurrentProfileToTop(); Firmware::setCurrentVariant(Firmware::getFirmwareForId(g.currentProfile().fwType())); emit firmwareChanged(); + updateFactories->radioProfileChanged(); QApplication::clipboard()->clear(); updateMenus(); } diff --git a/companion/src/progressdialog.cpp b/companion/src/progressdialog.cpp index ce2c28cf1f6..02b5b4e94bb 100644 --- a/companion/src/progressdialog.cpp +++ b/companion/src/progressdialog.cpp @@ -1,7 +1,8 @@ /* - * Copyright (C) OpenTX + * 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 @@ -27,7 +28,8 @@ ProgressDialog::ProgressDialog(QWidget *parent, const QString &title, const QIcon &icon, bool forceOpen): QDialog(parent), ui(new Ui::ProgressDialog), -locked(false) +locked(false), +keepOpen(false) { ui->setupUi(this); setWindowTitle(title); @@ -55,7 +57,8 @@ void ProgressDialog::on_closeButton_clicked() if (!locked) { ui->outputProgress->stop(); emit rejected(); - close(); + if (!keepOpen) + close(); } } @@ -89,3 +92,9 @@ void ProgressDialog::setProcessStopped() { ui->closeButton->setText(tr("Close")); } + +void ProgressDialog::on_outputProgress_keepOpen(bool val) +{ + keepOpen = val; +} + diff --git a/companion/src/progressdialog.h b/companion/src/progressdialog.h index 1aef7647793..5633a575dd9 100644 --- a/companion/src/progressdialog.h +++ b/companion/src/progressdialog.h @@ -1,7 +1,8 @@ /* - * Copyright (C) OpenTX + * 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 @@ -18,8 +19,7 @@ * GNU General Public License for more details. */ -#ifndef _PROGRESSDIALOG_H_ -#define _PROGRESSDIALOG_H_ +#pragma once #include @@ -34,26 +34,26 @@ class ProgressDialog : public QDialog { Q_OBJECT -public: - ProgressDialog(QWidget *parent, const QString &label, const QIcon &icon, bool forceOpen=false); - ~ProgressDialog(); + public: + ProgressDialog(QWidget *parent, const QString &label, const QIcon &icon, bool forceOpen=false); + ~ProgressDialog(); - ProgressWidget * progress(); - bool isEmpty() const; + ProgressWidget * progress(); + bool isEmpty() const; -public slots: - void setProcessStarted(); - void setProcessStopped(); + public slots: + void setProcessStarted(); + void setProcessStopped(); -private slots: - void on_closeButton_clicked(); - void on_outputProgress_detailsToggled(); - void on_outputProgress_locked(bool); - void shrink(); + private slots: + void on_closeButton_clicked(); + void on_outputProgress_detailsToggled(); + void on_outputProgress_locked(bool); + void on_outputProgress_keepOpen(bool); + void shrink(); -private: - Ui::ProgressDialog *ui; - bool locked; + private: + Ui::ProgressDialog *ui; + bool locked; + bool keepOpen; }; - -#endif // _PROGRESSDIALOG_H_ diff --git a/companion/src/progresswidget.cpp b/companion/src/progresswidget.cpp index 92f45e6f761..2f192a9a3b6 100644 --- a/companion/src/progresswidget.cpp +++ b/companion/src/progresswidget.cpp @@ -134,10 +134,10 @@ void ProgressWidget::addMessage(const QString & text, const int & type, bool ric color = "dimgrey"; // not important messages, may be filtered out break; case QtWarningMsg: // use warning level as emphasis - color = "darkblue"; + color = "orangered"; break; case QtCriticalMsg: // use critical as a warning - color = "#ff7900"; + color = "darkred"; break; case QtFatalMsg: // fatal for hard errors color = "red"; @@ -206,3 +206,8 @@ void ProgressWidget::lock(bool lock) { emit locked(lock); } + +void ProgressWidget::forceKeepOpen(bool value) +{ + emit keepOpen(value); +} diff --git a/companion/src/progresswidget.h b/companion/src/progresswidget.h index 85852b8f5e6..3329f8c5e85 100644 --- a/companion/src/progresswidget.h +++ b/companion/src/progresswidget.h @@ -51,11 +51,13 @@ class ProgressWidget : public QWidget void forceOpen(); void stop(); void clearDetails() const; + void forceKeepOpen(bool value); signals: void detailsToggled(); void locked(bool); void stopped(); + void keepOpen(bool); protected slots: void toggleDetails(); diff --git a/companion/src/storage/appdata.h b/companion/src/storage/appdata.h index c544058b6fd..a69103a1142 100644 --- a/companion/src/storage/appdata.h +++ b/companion/src/storage/appdata.h @@ -492,7 +492,6 @@ class Profile: public CompStoreObj PROPERTY4(int, defaultMode, "default_mode", 1) PROPERTY (int, volumeGain, 10) - PROPERTY4(bool, renameFwFiles, "rename_firmware_files", false) PROPERTY (bool, burnFirmware, false) PROPERTY (bool, penableBackup, false) PROPERTY (bool, runSDSync, false) diff --git a/companion/src/updates/CMakeLists.txt b/companion/src/updates/CMakeLists.txt index 72e653ccf61..f98a95ae823 100644 --- a/companion/src/updates/CMakeLists.txt +++ b/companion/src/updates/CMakeLists.txt @@ -3,12 +3,14 @@ project(updates) set(${PROJECT_NAME}_NAMES chooserdialog - downloaddialog repo repoassets + repobuild repodatamodels + repogithub repometadata reporeleases + updatecloudbuild updatecompanion updatefactories updatefirmware diff --git a/companion/src/updates/downloaddialog.cpp b/companion/src/updates/downloaddialog.cpp deleted file mode 100644 index db691f98a5c..00000000000 --- a/companion/src/updates/downloaddialog.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 "downloaddialog.h" -#include "ui_downloaddialog.h" -#include "constants.h" -#include "helpers.h" - -#include - -DownloadDialog::DownloadDialog(QWidget *parent, QString src, QString tgt, QString contentType, QString title): - QDialog(parent), - ui(new Ui::DownloadDialog), - reply(nullptr), - file(nullptr), - aborted(false) -{ - ui->setupUi(this); - setWindowIcon(CompanionIcon("download.png")); - ui->progressBar->setValue(0); - ui->progressBar->setMinimum(0); - ui->progressBar->setMaximum(100); - - if (tgt.isEmpty()) { - setWindowTitle(src); - return; // just show wait dialog. - } - - setWindowTitle(windowTitle() % title); - - file = new QFile(tgt); - if (!file->open(QIODevice::WriteOnly)) { - QMessageBox::critical(this, CPN_STR_APP_NAME, tr("Unable to open the download file %1 for writing.\nError: %2").arg(tgt).arg(file->errorString())); - QTimer::singleShot(0, this, SLOT(fileError())); - } - else { - url.setUrl(src); - qDebug() << "url:" << url.url(); - request.setUrl(url); - request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork); - request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); - - request.setRawHeader(QByteArray("Accept"), contentType.toUtf8()); - - reply = qnam.get(request); - connect(reply, SIGNAL(finished()), this, SLOT(httpFinished())); - connect(reply, SIGNAL(readyRead()), this, SLOT(httpReadyRead())); - connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(updateDataReadProgress(qint64, qint64))); - } -} - -DownloadDialog::~DownloadDialog() -{ - delete ui; - delete file; -} - -void DownloadDialog::reject() -{ - if (reply && reply->isRunning()) { - aborted = true; - reply->abort(); // this will call QNetworkReply::finished() - return; - } - - QDialog::reject(); -} - -void DownloadDialog::httpFinished() -{ - file->flush(); - file->close(); - - const bool ok = !(reply->error() || aborted); - - if (!ok) { - file->remove(); - if (!aborted) - QMessageBox::information(this, CPN_STR_APP_NAME, tr("Download failed: %1.").arg(reply->errorString())); - } - - reply->deleteLater(); - reply = nullptr; - file->deleteLater(); - file = nullptr; - - if (ok) - accept(); - else - reject(); -} - -void DownloadDialog::httpReadyRead() -{ - if (file) { - file->write(reply->readAll()); - } -} - -void DownloadDialog::updateDataReadProgress(qint64 bytesRead, qint64 totalBytes) -{ - ui->progressBar->setMaximum(totalBytes); - ui->progressBar->setValue(bytesRead); -} - -void DownloadDialog::fileError() -{ - delete file; - file = nullptr; - reject(); -} diff --git a/companion/src/updates/downloaddialog.h b/companion/src/updates/downloaddialog.h deleted file mode 100644 index 1ea6d822cd3..00000000000 --- a/companion/src/updates/downloaddialog.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace Ui { - class DownloadDialog; -} - -class DownloadDialog : public QDialog -{ - Q_OBJECT - - public: - explicit DownloadDialog(QWidget *parent = 0, QString src = "", QString tgt = "", QString contentType = "", QString title = ""); - - ~DownloadDialog(); - - public slots: - virtual void reject() override; - - private slots: - void fileError(); - void httpFinished(); - void httpReadyRead(); - void updateDataReadProgress(qint64 bytesRead, qint64 totalBytes); - - private: - Ui::DownloadDialog *ui; - QNetworkAccessManager qnam; - QNetworkReply *reply; - QFile *file; - bool aborted; - QNetworkRequest request; - QUrl url; -}; diff --git a/companion/src/updates/downloaddialog.ui b/companion/src/updates/downloaddialog.ui deleted file mode 100644 index 280611d756d..00000000000 --- a/companion/src/updates/downloaddialog.ui +++ /dev/null @@ -1,86 +0,0 @@ - - - DownloadDialog - - - - 0 - 0 - 562 - 82 - - - - Downloading: - - - - - - 0 - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - buttonBox - accepted() - DownloadDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - DownloadDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/companion/src/updates/repo.cpp b/companion/src/updates/repo.cpp index 8022a715298..f3ad9562e9d 100644 --- a/companion/src/updates/repo.cpp +++ b/companion/src/updates/repo.cpp @@ -21,12 +21,17 @@ #include "repo.h" -Repo::Repo(QObject * parent, UpdateStatus * status, UpdateNetwork * network) : +Repo::Repo(QObject * parent, UpdateStatus * status, UpdateNetwork * network, + const QString & path, const QString & nightly, const int resultsPerPage) : QObject(parent), m_status(status), m_network(network), m_releases(new RepoReleases(this, status, network)), - m_assets(new RepoAssets(this, status, network)) + m_assets(new RepoAssets(this, status, network)), + m_path(path), + m_nightly(nightly), + m_resultsPerPage(resultsPerPage), + m_config(nullptr) { connect(m_releases, &RepoReleases::idChanged, [=](const int id) { m_assets->onReleaseIdChanged(id); @@ -37,11 +42,13 @@ Repo::~Repo() { delete m_assets; delete m_releases; + if (m_config) + delete m_config; } bool Repo::getJson(const QString filename, QJsonDocument * json) { - m_network->downloadJsonContent(m_releases->urlContent(filename), json); + m_network->downloadJsonContent(urlContent(filename), json); if (!m_network->isSuccess()) { m_status->reportProgress("Unable to download json data", QtDebugMsg); @@ -51,9 +58,10 @@ bool Repo::getJson(const QString filename, QJsonDocument * json) return true; } -void Repo::init(const QString & repoPath, const QString & nightly, const int resultsPerPage) +void Repo::setConfig(const QJsonObject & config) { - m_path = repoPath; - m_releases->init(repoPath, nightly, resultsPerPage); - m_assets->init(repoPath, QString(), resultsPerPage); + if (m_config) + delete m_config; + + m_config = new QJsonObject(config); } diff --git a/companion/src/updates/repo.h b/companion/src/updates/repo.h index cb63ac0dac1..31f6c03dfc1 100644 --- a/companion/src/updates/repo.h +++ b/companion/src/updates/repo.h @@ -27,6 +27,7 @@ #include "repoassets.h" #include +#include constexpr char GH_API_REPOS[] {"https://api.github.com/repos"}; constexpr char GH_API_REPOS_EDGETX[] {"https://api.github.com/repos/EdgeTX"}; @@ -36,14 +37,39 @@ class Repo : public QObject Q_OBJECT public: - explicit Repo(QObject * parent, UpdateStatus * status, UpdateNetwork * network); + enum RepoType { + REPO_TYPE_GITHUB, + REPO_TYPE_BUILD, + }; + Q_ENUM(RepoType) + + explicit Repo(QObject * parent, UpdateStatus * status, UpdateNetwork * network, + const QString & repoPath, const QString & nightly, const int resultsPerPage); virtual ~Repo(); + virtual const QString urlAsset(const int assetId) const = 0; + virtual const QString urlAssets(const int releaseId) const = 0; + virtual const QString urlContent(const QString & filename) const = 0; + virtual const QString urlJobs() const = 0; + virtual const QString urlReleases() const = 0; + virtual const QString urlStatus() const = 0; + + virtual const UpdateNetwork::DownloadDataType assetDownloadDataType() const { return UpdateNetwork::DDT_Unknown; } + virtual const UpdateNetwork::DownloadDataType assetContentDataType() const { return UpdateNetwork::DDT_Unknown; } + + virtual const RepoRawItemModel::MetaDataType assetMetaDataType() const { return RepoRawItemModel::MDT_Unknown; } + virtual const RepoRawItemModel::MetaDataType assetsMetaDataType() const { return RepoRawItemModel::MDT_Unknown; } + virtual const RepoRawItemModel::MetaDataType releasesMetaDataType() const { return RepoRawItemModel::MDT_Unknown; } + + void setConfig(const QJsonObject & config); + QJsonObject* config() { return m_config; } + RepoAssets* const assets() const { return m_assets; } bool getJson(const QString filename, QJsonDocument * json); - void init(const QString & repoPath, const QString & nightly, const int resultsPerPage); + const QString nightly() const { return m_nightly; } const QString path() const { return m_path; } RepoReleases* const releases() const { return m_releases; } + const int resultsPerPage() const { return m_resultsPerPage; } private: UpdateStatus* const m_status; @@ -51,4 +77,7 @@ class Repo : public QObject RepoReleases* const m_releases; RepoAssets* const m_assets; QString m_path; + QString m_nightly; + int m_resultsPerPage; + QJsonObject *m_config; }; diff --git a/companion/src/updates/repoassets.cpp b/companion/src/updates/repoassets.cpp index d7eb3c97496..71c6cf16842 100644 --- a/companion/src/updates/repoassets.cpp +++ b/companion/src/updates/repoassets.cpp @@ -19,24 +19,63 @@ * GNU General Public License for more details. */ -#include "repoassets.h" +#include "repotypes.h" /* AssetsRawItemModel */ -AssetsRawItemModel::AssetsRawItemModel() : - RepoRawItemModel("Raw Assets") +AssetsRawItemModel::AssetsRawItemModel(QObject * parentRepo) : + RepoRawItemModel("Raw Assets"), + m_parentRepo(parentRepo) { } void AssetsRawItemModel::parseJsonObject(const QJsonObject & obj) +{ + //qDebug() << obj; + RepoGitHub *rg = dynamic_cast(m_parentRepo); + if (rg) { + parseJsonObjectGitHub(obj); + } + else { + RepoBuild *rb = dynamic_cast(m_parentRepo); + if (rb) { + parseJsonObjectBuild(); + } + } +} + +void AssetsRawItemModel::parseJsonObjectBuild() +{ + Repo *repo = static_cast(m_parentRepo); + + if (repo->config()->value("targets").isObject()) { + const QJsonObject &targets = repo->config()->value("targets").toObject(); + QStringList list = targets.keys(); + int id = 0; + + for (auto i = list.cbegin(), end = list.cend(); i != end; ++i) { + QStandardItem * item = new QStandardItem(); + + item->setText(*i); + item->setData(++id, RIMR_Id); + item->setData(true, RIMR_Available); + item->setData(0, RIMR_Flags); + + appendRow(item); + } + } +} + +void AssetsRawItemModel::parseJsonObjectGitHub(const QJsonObject & obj) { QStandardItem * item = new QStandardItem(); if (!obj.value("name").isUndefined()) { item->setText(obj.value("name").toString()); + item->setData(obj.value("name").toString(), RIMR_DownloadName); } if (!obj.value("id").isUndefined()) { @@ -57,12 +96,23 @@ void AssetsRawItemModel::parseJsonObject(const QJsonObject & obj) appendRow(item); } +bool AssetsRawItemModel::setDownloadUrl(const int id, const QString url) +{ + return setValue(id, RIMR_DownloadUrl, QVariant(url)); +} + +bool AssetsRawItemModel::setDownloadName(const int id, const QString name) +{ + return setValue(id, RIMR_DownloadName, QVariant(name)); +} + /* AssetsFilteredItemModel */ -AssetsFilteredItemModel::AssetsFilteredItemModel(AssetsRawItemModel * assetsRawItemModel) : - RepoFilteredItemModel("Filtered Assets") +AssetsFilteredItemModel::AssetsFilteredItemModel(QObject * parentRepo, AssetsRawItemModel * assetsRawItemModel) : + RepoFilteredItemModel("Filtered Assets"), + m_parentRepo(parentRepo) { setSourceModel(assetsRawItemModel); } @@ -77,6 +127,16 @@ const QString AssetsFilteredItemModel::copyFilter(const int id) const return value(id, RIMR_CopyFilter).toString(); } +const QString AssetsFilteredItemModel::downloadUrl(const int id) const +{ + return value(id, RIMR_DownloadUrl).toString(); +} + +const QString AssetsFilteredItemModel::downloadName(const int id) const +{ + return value(id, RIMR_DownloadName).toString(); +} + bool AssetsFilteredItemModel::setCopyFilter(const int id, const QString filter) { return setValue(id, RIMR_CopyFilter, QVariant(filter)); @@ -96,15 +156,23 @@ const QString AssetsFilteredItemModel::subDirectory(const int id) const RepoAssets */ -RepoAssets::RepoAssets(QObject * parent, UpdateStatus * status, UpdateNetwork * network) : - QObject(parent), +RepoAssets::RepoAssets(QObject * parentRepo, UpdateStatus * status, UpdateNetwork * network) : + QObject(parentRepo), RepoMetaData(status, network), - m_rawItemModel(new AssetsRawItemModel()), - m_filteredItemModel(new AssetsFilteredItemModel(m_rawItemModel)) + m_rawItemModel(new AssetsRawItemModel(parentRepo)), + m_filteredItemModel(new AssetsFilteredItemModel(parentRepo, m_rawItemModel)), + m_parentRepo(parentRepo) + { setModels(m_rawItemModel, m_filteredItemModel); } +bool RepoAssets::build(const int row) +{ + status()->reportProgress(tr("Generic asset build not implemented"), QtDebugMsg); + return true; +} + const QString RepoAssets::contentType() const { return m_filteredItemModel->contentType(id()); @@ -117,21 +185,46 @@ const QString RepoAssets::copyFilter() const bool RepoAssets::downloadToBuffer(const int id) { - //progressMessage(tr("Download asset %1").arg(id)); if (id > 0) setId(id); - network()->downloadToBuffer(UpdateNetwork::DDT_Content, urlAsset(this->id())); + + Repo *repo = static_cast(m_parentRepo); + network()->downloadToBuffer(repo->assetDownloadDataType(), repo->urlAsset(this->id())); return network()->isSuccess(); } bool RepoAssets::downloadToFile(const int row, QString & downloadDir) { + Repo *repo = static_cast(m_parentRepo); getSetId(row); - QFileInfo f(QString("%1/A%2/%3").arg(downloadDir).arg(id()).arg(name())); - network()->downloadToFile(urlAsset(id()), f.absoluteFilePath()); + QString fmt = QString("%1/A%2/%3").arg(downloadDir).arg(id()).arg(downloadName()); + QFileInfo f(fmt); + network()->downloadToFile(repo->assetDownloadDataType(), repo->urlAsset(id()), f.absoluteFilePath()); return network()->isSuccess(); } +const QString RepoAssets::downloadUrl() const +{ + return m_filteredItemModel->downloadUrl(id()); +} + +const QString RepoAssets::downloadUrl(const int row) +{ + getSetId(row); + return downloadUrl(); +} + +const QString RepoAssets::downloadName() const +{ + return m_filteredItemModel->downloadName(id()); +} + +const QString RepoAssets::downloadName(const int row) +{ + getSetId(row); + return downloadName(); +} + void RepoAssets::dumpItemModel(const QString modelName, const QAbstractItemModel * itemModel) const { qDebug() << "Contents of model:" << modelName; @@ -153,7 +246,8 @@ void RepoAssets::dumpItemModel(const QString modelName, const QAbstractItemModel bool RepoAssets::getJson(QJsonDocument * json) { - network()->downloadJsonAsset(urlAsset(id()), json); + Repo *repo = static_cast(m_parentRepo); + network()->downloadJsonAsset(repo->urlAsset(id()), json); if (!network()->isSuccess()) { status()->reportProgress("Unable to download json data", QtDebugMsg); @@ -171,9 +265,15 @@ bool RepoAssets::getJson(const QString assetName, QJsonDocument * json) return getJson(json); } -void RepoAssets::init(const QString & repoPath,const QString & nightly, const int resultsPerPage) +const QString RepoAssets::name() const { - RepoMetaData::init(repoPath, resultsPerPage); + return m_filteredItemModel->name(id()); +} + +const QString RepoAssets::name(const int row) +{ + getSetId(row); + return name(); } void RepoAssets::onReleaseIdChanged(const int id) @@ -184,13 +284,16 @@ void RepoAssets::onReleaseIdChanged(const int id) bool RepoAssets::retrieveMetaDataAll() { - return retrieveMetaData(RepoRawItemModel::MDT_Assets, urlAssets(m_releaseId)); + Repo *repo = static_cast(m_parentRepo); + return retrieveMetaData(repo->assetsMetaDataType(), repo->urlAssets(m_releaseId)); } bool RepoAssets::retrieveMetaDataOne(const int id) { setId(id); - return retrieveMetaData(RepoRawItemModel::MDT_Asset, urlAsset(id)); + + Repo *repo = static_cast(m_parentRepo); + return retrieveMetaData(repo->assetMetaDataType(), repo->urlAsset(id)); } bool RepoAssets::setCopyFilter(const QString filter) @@ -207,3 +310,13 @@ const QString RepoAssets::subDirectory() const { return m_filteredItemModel->subDirectory(id()); } + +bool RepoAssets::setDownloadUrl(const QString url) +{ + return m_rawItemModel->setDownloadUrl(id(), url); +} + +bool RepoAssets::setDownloadName(const QString name) +{ + return m_rawItemModel->setDownloadName(id(), name); +} diff --git a/companion/src/updates/repoassets.h b/companion/src/updates/repoassets.h index 4ed315873a2..a004bdaed47 100644 --- a/companion/src/updates/repoassets.h +++ b/companion/src/updates/repoassets.h @@ -29,13 +29,22 @@ class AssetsRawItemModel : public RepoRawItemModel Q_OBJECT public: - explicit AssetsRawItemModel(); + explicit AssetsRawItemModel(QObject * parentRepo); virtual ~AssetsRawItemModel() {} protected: friend class RepoAssets; virtual void parseJsonObject(const QJsonObject & obj); + + bool setDownloadUrl(const int id, const QString url); + bool setDownloadName(const int id, const QString name); + + private: + QObject *m_parentRepo; // cannot use Repo class from repo.h as compile error due to cyclic headers + + void parseJsonObjectGitHub(const QJsonObject & obj); + void parseJsonObjectBuild(); }; class AssetsFilteredItemModel : public RepoFilteredItemModel @@ -43,7 +52,7 @@ class AssetsFilteredItemModel : public RepoFilteredItemModel Q_OBJECT public: - explicit AssetsFilteredItemModel(AssetsRawItemModel * assetsRawItemModel); + explicit AssetsFilteredItemModel(QObject * parentRepo, AssetsRawItemModel * assetsRawItemModel); virtual ~AssetsFilteredItemModel() {}; protected: @@ -54,6 +63,11 @@ class AssetsFilteredItemModel : public RepoFilteredItemModel bool setCopyFilter(const int id, const QString filter); bool setSubDirectory(const int id, const QString path); const QString subDirectory(const int id) const; + const QString downloadUrl(const int id) const; + const QString downloadName(const int id) const; + + private: + QObject *m_parentRepo; // cannot use Repo class from repo.h as compile error due to cyclic headers }; class RepoAssets : public QObject, public RepoMetaData @@ -67,19 +81,27 @@ class RepoAssets : public QObject, public RepoMetaData explicit RepoAssets(QObject * parent, UpdateStatus * status, UpdateNetwork * network); virtual ~RepoAssets() {}; - virtual void init(const QString & repoPath, const QString & nightly, const int resultsPerPage); virtual bool retrieveMetaDataAll(); virtual bool retrieveMetaDataOne(const int id); + bool build(const int row); const QString contentType() const; const QString copyFilter() const; bool downloadToBuffer(const int id = 0); bool downloadToFile(const int row, QString & downloadDir); bool getJson(QJsonDocument * json); 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); signals: void idChanged(int id); @@ -87,6 +109,7 @@ class RepoAssets : public QObject, public RepoMetaData private: AssetsRawItemModel* const m_rawItemModel; AssetsFilteredItemModel* const m_filteredItemModel; + QObject *m_parentRepo; // cannot use Repo class from repo.h as compile error due to cyclic headers int m_releaseId; virtual void dumpItemModel(const QString modelName, const QAbstractItemModel * itemModel) const; diff --git a/companion/src/updates/repobuild.cpp b/companion/src/updates/repobuild.cpp new file mode 100644 index 00000000000..1c5ad152dc1 --- /dev/null +++ b/companion/src/updates/repobuild.cpp @@ -0,0 +1,52 @@ +/* + * 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 "repobuild.h" + +RepoBuild::RepoBuild(QObject * parent, UpdateStatus * status, UpdateNetwork * network, + const QString & path, const QString & nightly, const int resultsPerPage) : + Repo(parent, status, network, path, nightly, resultsPerPage) +{ +} + +RepoBuild::~RepoBuild() +{ +} + +const QString RepoBuild::urlAsset(const int assetId) const +{ + return QString(assets()->downloadUrl()); +} + +const QString RepoBuild::urlJobs() const +{ + return QString("%1/jobs").arg(path()); +} + +const QString RepoBuild::urlReleases() const +{ + return QString("%1/targets").arg(path()); +} + +const QString RepoBuild::urlStatus() const +{ + return QString("%1/status").arg(path()); +} diff --git a/companion/src/updates/repobuild.h b/companion/src/updates/repobuild.h new file mode 100644 index 00000000000..e1ea34e17b7 --- /dev/null +++ b/companion/src/updates/repobuild.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#pragma once + +#include "repo.h" + +class RepoBuild : public Repo +{ + Q_OBJECT + + public: + explicit RepoBuild(QObject * parent, UpdateStatus * status, UpdateNetwork * network, + const QString & path, const QString & nightly, const int resultsPerPage); + virtual ~RepoBuild(); + + const QString urlAsset(const int assetId) const; + const QString urlAssets(const int releaseId) const { return ""; } + const QString urlContent(const QString & filename) const { return ""; } + const QString urlJobs() const; + const QString urlReleases() const; + const QString urlStatus() const; + + const UpdateNetwork::DownloadDataType assetDownloadDataType() const override { return UpdateNetwork::DDT_Build_SaveToFile; } + const UpdateNetwork::DownloadDataType assetContentDataType() const override { return UpdateNetwork::DDT_Unknown; } + + const RepoRawItemModel::MetaDataType assetMetaDataType() const override { return RepoRawItemModel::MDT_Build_Asset; } + const RepoRawItemModel::MetaDataType assetsMetaDataType() const override { return RepoRawItemModel::MDT_Build_Assets; } + const RepoRawItemModel::MetaDataType releasesMetaDataType() const override { return RepoRawItemModel::MDT_Build_Releases; } + + private: +}; diff --git a/companion/src/updates/repodatamodels.cpp b/companion/src/updates/repodatamodels.cpp index d8e7fa2e551..937b258c72f 100644 --- a/companion/src/updates/repodatamodels.cpp +++ b/companion/src/updates/repodatamodels.cpp @@ -59,12 +59,15 @@ void RepoRawItemModel::parseAll(QJsonDocument * json) void RepoRawItemModel::parseMetaData(const int mdt, QJsonDocument * json) { switch (mdt) { - case MDT_Asset: - case MDT_Release: + case MDT_Build_Asset: + case MDT_Build_Assets: + case MDT_Build_Release: + case MDT_Build_Releases: + case MDT_GitHub_Asset: parseOne(json); break; - case MDT_Assets: - case MDT_Releases: + case MDT_GitHub_Assets: + case MDT_GitHub_Releases: parseAll(json); break; default: @@ -84,6 +87,17 @@ void RepoRawItemModel::parseOne(QJsonDocument * json) } } +bool RepoRawItemModel::setValue(const int id, const int role, const QVariant value) +{ + //qDebug() << "id:" << id << "role:" << role << "value:" << value; + const QModelIndexList idxlst = match(index(0, 0), RIMR_Id, QVariant(id), 1, Qt::MatchExactly); + + if (!idxlst.isEmpty()) + return setData(idxlst.at(0), value, role); + + return false; +} + // static QString RepoRawItemModel::typeToString(const int val) { diff --git a/companion/src/updates/repodatamodels.h b/companion/src/updates/repodatamodels.h index 416313399c5..99178658da8 100644 --- a/companion/src/updates/repodatamodels.h +++ b/companion/src/updates/repodatamodels.h @@ -44,7 +44,9 @@ class RepoItemModelBase RIMR_Available, RIMR_Flags, RIMR_CopyFilter, - RIMR_SubDirectory + RIMR_SubDirectory, + RIMR_DownloadUrl, + RIMR_DownloadName, }; private: @@ -60,10 +62,19 @@ class RepoRawItemModel : public QStandardItemModel, public RepoItemModelBase virtual ~RepoRawItemModel() {} enum MetaDataType { - MDT_Asset, - MDT_Assets, - MDT_Release, - MDT_Releases + MDT_Unknown, + MDT_GitHub_Asset, + MDT_GitHub_First = MDT_GitHub_Asset, + MDT_GitHub_Assets, + MDT_GitHub_Release, + MDT_GitHub_Releases, + MDT_GitHub_Last = MDT_GitHub_Releases, + MDT_Build_Asset, + MDT_Build_First = MDT_Build_Asset, + MDT_Build_Assets, + MDT_Build_Release, + MDT_Build_Releases, + MDT_Build_Last = MDT_Build_Releases, }; static QString typeToString(const int val); @@ -83,6 +94,7 @@ class RepoRawItemModel : public QStandardItemModel, public RepoItemModelBase void parseMetaData(const int mdt, QJsonDocument * json); void parseOne(QJsonDocument * json); void setRefreshRequired(const bool val) { m_refreshRequired = val; } + bool setValue(const int id, const int role, const QVariant value); void update(); private: diff --git a/companion/src/updates/repogithub.cpp b/companion/src/updates/repogithub.cpp new file mode 100644 index 00000000000..b79a5998079 --- /dev/null +++ b/companion/src/updates/repogithub.cpp @@ -0,0 +1,53 @@ +/* + * 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 "repogithub.h" + +RepoGitHub::RepoGitHub(QObject * parent, UpdateStatus * status, UpdateNetwork * network, + const QString & path, const QString & nightly, const int resultsPerPage) : + Repo(parent, status, network, path, nightly, resultsPerPage) +{ +} + +RepoGitHub::~RepoGitHub() +{ +} + +const QString RepoGitHub::urlAsset(const int assetId) const +{ + return QString("%1/assets/%2").arg(urlReleases()).arg(assetId); +} + +const QString RepoGitHub::urlAssets(const int releaseId) const +{ + return QString("%1/%2/assets").arg(urlReleases()).arg(releaseId) % (resultsPerPage() > -1 ? + QString("\?per_page=%1").arg(resultsPerPage()) : ""); +} + +const QString RepoGitHub::urlContent(const QString & filename) const +{ + return QString("%1/contents/%2").arg(path()).arg(filename); +} + +const QString RepoGitHub::urlReleases() const +{ + return QString("%1/releases").arg(path()); +} diff --git a/companion/src/updates/repogithub.h b/companion/src/updates/repogithub.h new file mode 100644 index 00000000000..4e5bc6c62a5 --- /dev/null +++ b/companion/src/updates/repogithub.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#pragma once + +#include "repo.h" + +class RepoGitHub : public Repo +{ + Q_OBJECT + + public: + explicit RepoGitHub(QObject * parent, UpdateStatus * status, UpdateNetwork * network, + const QString & path, const QString & nightly, const int resultsPerPage); + virtual ~RepoGitHub(); + + const QString urlAsset(const int assetId) const; + const QString urlAssets(const int releaseId) const; + const QString urlContent(const QString & filename) const; + const QString urlJobs() const { return ""; } + const QString urlReleases() const; + const QString urlStatus() const { return ""; } + + const UpdateNetwork::DownloadDataType assetDownloadDataType() const override { return UpdateNetwork::DDT_GitHub_SaveToFile; } + const UpdateNetwork::DownloadDataType assetContentDataType() const override { return UpdateNetwork::DDT_GitHub_Content; } + + const RepoRawItemModel::MetaDataType assetMetaDataType() const override { return RepoRawItemModel::MDT_GitHub_Asset; } + const RepoRawItemModel::MetaDataType assetsMetaDataType() const override { return RepoRawItemModel::MDT_GitHub_Assets; } + const RepoRawItemModel::MetaDataType releasesMetaDataType() const override { return RepoRawItemModel::MDT_GitHub_Releases; } + + private: +}; diff --git a/companion/src/updates/repometadata.cpp b/companion/src/updates/repometadata.cpp index 0246abd62e3..86e525e5934 100644 --- a/companion/src/updates/repometadata.cpp +++ b/companion/src/updates/repometadata.cpp @@ -76,12 +76,6 @@ const int RepoMetaData::id() const return m_id; } -void RepoMetaData::init(const QString & repoPath, const int resultsPerPage) -{ - m_repoPath = repoPath; - m_resultsPerPage = resultsPerPage; -} - void RepoMetaData::invalidate() { setId(0); @@ -113,16 +107,6 @@ UpdateNetwork* const RepoMetaData::network() const return m_network; } -const QString RepoMetaData::repoPath() const -{ - return m_repoPath; -} - -const int RepoMetaData::resultsPerPage() const -{ - return m_resultsPerPage; -} - bool RepoMetaData::retrieveMetaData(const int mdt, const QString url) { if (!m_rawItemModel->isRefreshRequired()) @@ -130,12 +114,21 @@ bool RepoMetaData::retrieveMetaData(const int mdt, const QString url) QJsonDocument *json = new QJsonDocument(); - network()->downloadMetaData(url, json); - - if (!network()->isSuccess()) { - status()->reportProgress("Unable to download release asset meta data", QtDebugMsg); - invalidate(); - return false; + if (!url.isEmpty()) { + network()->downloadMetaData(mdt >= RepoRawItemModel::MDT_GitHub_First && + mdt <= RepoRawItemModel::MDT_GitHub_Last ? UpdateNetwork::DDT_GitHub_MetaData : + UpdateNetwork::DDT_Build_MetaData, + url, + json); + + if (!network()->isSuccess()) { + status()->reportProgress("Unable to download release asset meta data", QtDebugMsg); + invalidate(); + return false; + } + } + else { + json->setObject(QJsonObject()); // add a dummy object so later tests pass } m_rawItemModel->parseMetaData(mdt, json); @@ -180,24 +173,3 @@ UpdateStatus* const RepoMetaData::status() const { return m_status; } - -const QString RepoMetaData::urlAsset(const int assetId) const -{ - return QString("%1/assets/%2").arg(urlReleases()).arg(assetId); -} - -const QString RepoMetaData::urlAssets(const int releaseId) const -{ - return QString("%1/%2/assets").arg(urlReleases()).arg(releaseId) % (m_resultsPerPage > -1 ? - QString("\?per_page=%1").arg(m_resultsPerPage) : ""); -} - -const QString RepoMetaData::urlContent(const QString & filename) const -{ - return QString("%1/contents/%2").arg(m_repoPath).arg(filename); -} - -const QString RepoMetaData::urlReleases() const -{ - return QString("%1/releases").arg(m_repoPath); -} diff --git a/companion/src/updates/repometadata.h b/companion/src/updates/repometadata.h index f897b450caf..0fe5a344773 100644 --- a/companion/src/updates/repometadata.h +++ b/companion/src/updates/repometadata.h @@ -35,7 +35,6 @@ class RepoMetaData explicit RepoMetaData(UpdateStatus * status, UpdateNetwork * network); virtual ~RepoMetaData() {} - virtual void init(const QString & repoPath, const QString & nightly, const int resultsPerPage) = 0; virtual bool retrieveMetaDataAll() = 0; virtual bool retrieveMetaDataOne(const int id) = 0; virtual int setId(const int id); @@ -49,25 +48,18 @@ class RepoMetaData int getSetId(const int row); int getSetId(QVariant value, Qt::MatchFlags flags = Qt::MatchExactly, int role = Qt::DisplayRole); const int id() const; - void init(const QString & repoPath, const int resultsPerPage); void invalidate(); const int isEmpty() const; const bool isIdValid() const; const QStringList list() const; const QString name() const; UpdateNetwork* const network() const; - const QString repoPath() const; - const int resultsPerPage() const; bool retrieveMetaData(const int mdt, const QString url); bool setFlags(int flags); void setFilterFlags(int flags); void setFilterPattern(const QString & pattern); void setModels(RepoRawItemModel * repoRawItemModel, RepoFilteredItemModel * repoFilteredItemModel); UpdateStatus* const status() const; - const QString urlAsset(const int assetId) const; - const QString urlAssets(const int releaseId) const; - const QString urlContent(const QString & filename) const; - const QString urlReleases() const; private: UpdateStatus* const m_status; @@ -75,8 +67,6 @@ class RepoMetaData RepoRawItemModel* m_rawItemModel; RepoFilteredItemModel* m_filteredItemModel; - QString m_repoPath; - int m_resultsPerPage; int m_id; virtual void dumpItemModel(const QString modelName, const QAbstractItemModel * itemModel) const = 0; diff --git a/companion/src/updates/reporeleases.cpp b/companion/src/updates/reporeleases.cpp index f91ef691458..d75925e845d 100644 --- a/companion/src/updates/reporeleases.cpp +++ b/companion/src/updates/reporeleases.cpp @@ -19,7 +19,7 @@ * GNU General Public License for more details. */ -#include "reporeleases.h" +#include "repotypes.h" #include "appdata.h" #include "helpers.h" @@ -55,15 +55,96 @@ const QString ReleasesItemModels::flagToString(const int val) ReleasesRawItemModel */ -ReleasesRawItemModel::ReleasesRawItemModel() : +ReleasesRawItemModel::ReleasesRawItemModel(QObject * parentRepo) : RepoRawItemModel("Raw Releases"), - m_nightlyName("") + m_parentRepo(parentRepo) { setSortRole(RIMR_SortOrder); } void ReleasesRawItemModel::parseJsonObject(const QJsonObject & obj) { + //qDebug() << obj; + RepoGitHub *rg = dynamic_cast(m_parentRepo); + if (rg) { + parseJsonObjectGitHub(obj); + } + else { + RepoBuild *rb = dynamic_cast(m_parentRepo); + if (rb) { + parseJsonObjectBuild(obj); + } + } +} + +void ReleasesRawItemModel::parseJsonObjectBuild(const QJsonObject & obj) +{ + Repo *repo = static_cast(m_parentRepo); + const QString fwRadio = getCurrentFirmware()->getFlavour(); + + if (obj.value("releases").isObject()) { + repo->setConfig(obj); + const QJsonObject &releases = obj.value("releases").toObject(); + QStringList list = releases.keys(); + + for (auto i = list.cbegin(), end = list.cend(); i != end; ++i) { + QStandardItem * item = new QStandardItem(); + + int flags = RELFLG_Stable; + bool available = true; + + item->setText(*i); + item->setData(*i, RIMR_Tag); + + if (item->data(RIMR_Tag).toString().toLower() == repo->nightly().toLower()) + item->setData((int)QDate::currentDate().toJulianDay(), RIMR_Id); + else + item->setData(SemanticVersion(*i).toInt(), RIMR_Id); + + item->setData(QDateTime::currentDateTimeUtc(), RIMR_Date); + + SemanticVersion sv; + + if (item->data(RIMR_Tag).toString().toLower() == repo->nightly().toLower()) { + flags = RELFLG_Nightly; + sv.fromString("255.255.255"); + } + else + sv.fromString(item->data(RIMR_Tag).toString()); + + if (sv.isPreRelease()) + flags = RELFLG_PreRelease; + + if (sv.isValid()) + item->setData(sv.toInt(), RIMR_SortOrder); + else + item->setData(item->data(RIMR_Id).toInt(), RIMR_SortOrder); + + if (releases.value(*i).isObject()) { + const QJsonObject &release = releases.value(*i).toObject(); + if (!release.value("exclude_targets").isUndefined()) { + if (release.value("exclude_targets").isArray()) { + const QJsonArray &excluded = release.value("exclude_targets").toArray(); + foreach (const QJsonValue &v, excluded) { + if (v.isString() && v.toString() == fwRadio) { + available = false; + } + } + } + } + } + + item->setData(available, RIMR_Available); + item->setData(flags, RIMR_Flags); + + appendRow(item); + } + } +} + +void ReleasesRawItemModel::parseJsonObjectGitHub(const QJsonObject & obj) +{ + Repo *repo = static_cast(m_parentRepo); QStandardItem * item = new QStandardItem(); int flags = RELFLG_Stable; @@ -88,7 +169,7 @@ void ReleasesRawItemModel::parseJsonObject(const QJsonObject & obj) if (!obj.value("tag_name").isUndefined()) { item->setData(obj.value("tag_name").toString(), RIMR_Tag); - if (item->data(RIMR_Tag).toString().toLower() == m_nightlyName) + if (item->data(RIMR_Tag).toString().toLower() == repo->nightly().toLower()) flags = RELFLG_Nightly; } @@ -102,7 +183,7 @@ void ReleasesRawItemModel::parseJsonObject(const QJsonObject & obj) SemanticVersion sv; - if (item->data(RIMR_Tag).toString().toLower() == m_nightlyName) + if (item->data(RIMR_Tag).toString().toLower() == repo->nightly().toLower()) sv.fromString("255.255.255"); else sv.fromString(item->data(RIMR_Tag).toString()); @@ -115,20 +196,16 @@ void ReleasesRawItemModel::parseJsonObject(const QJsonObject & obj) appendRow(item); } -void ReleasesRawItemModel::setNightlyName(const QString name) -{ - m_nightlyName = name.toLower(); -} - /* ReleasesFilteredItemModel */ -ReleasesFilteredItemModel::ReleasesFilteredItemModel(ReleasesRawItemModel * releasesRawItemModel) : - RepoFilteredItemModel("Filtered Releases") +ReleasesFilteredItemModel::ReleasesFilteredItemModel(QObject * parentRepo, ReleasesRawItemModel * releasesRawItemModel) : + RepoFilteredItemModel("Filtered Releases"), + m_parentRepo(parentRepo) { setSourceModel(releasesRawItemModel); - setSortRole(RIMR_Date); + setSortRole(RIMR_SortOrder); } const int ReleasesFilteredItemModel::channelLatestId() const @@ -155,11 +232,12 @@ const QString ReleasesFilteredItemModel::version(const int id) const RepoReleases */ -RepoReleases::RepoReleases(QObject * parent, UpdateStatus * status, UpdateNetwork * network) : - QObject(parent), +RepoReleases::RepoReleases(QObject * parentRepo, UpdateStatus * status, UpdateNetwork * network) : + QObject(parentRepo), RepoMetaData(status, network), - m_rawItemModel(new ReleasesRawItemModel()), - m_filteredItemModel(new ReleasesFilteredItemModel(m_rawItemModel)) + m_rawItemModel(new ReleasesRawItemModel(parentRepo)), + m_filteredItemModel(new ReleasesFilteredItemModel(parentRepo, m_rawItemModel)), + m_parentRepo(parentRepo) { setModels(m_rawItemModel, m_filteredItemModel); } @@ -186,15 +264,16 @@ void RepoReleases::dumpItemModel(const QString modelName, const QAbstractItemMod } } -void RepoReleases::init(const QString & repoPath,const QString & nightly, const int resultsPerPage) -{ - RepoMetaData::init(repoPath, resultsPerPage); - m_rawItemModel->setNightlyName(nightly); -} - bool RepoReleases::retrieveMetaDataAll() { - return retrieveMetaData(RepoRawItemModel::MDT_Releases, urlReleases()); + bool res = false; + Repo *repo = dynamic_cast(m_parentRepo); + if (repo) { + res = retrieveMetaData(repo->releasesMetaDataType(), repo->urlReleases()); + m_rawItemModel->sort(0, Qt::DescendingOrder); + } + + return res; } int RepoReleases::setId(const int id) diff --git a/companion/src/updates/reporeleases.h b/companion/src/updates/reporeleases.h index a6ebd436d85..6af24a5f3ec 100644 --- a/companion/src/updates/reporeleases.h +++ b/companion/src/updates/reporeleases.h @@ -46,7 +46,7 @@ class ReleasesRawItemModel : public RepoRawItemModel, public ReleasesItemModels Q_OBJECT public: - explicit ReleasesRawItemModel(); + explicit ReleasesRawItemModel(QObject * parentRepo); virtual ~ReleasesRawItemModel() {} protected: @@ -54,10 +54,11 @@ class ReleasesRawItemModel : public RepoRawItemModel, public ReleasesItemModels virtual void parseJsonObject(const QJsonObject & obj); - void setNightlyName(const QString name); - private: - QString m_nightlyName; + QObject *m_parentRepo; // cannot use Repo class from repo.h as compile error due to cyclic headers + + void parseJsonObjectGitHub(const QJsonObject & obj); + void parseJsonObjectBuild(const QJsonObject & obj); }; class ReleasesFilteredItemModel: public RepoFilteredItemModel, public ReleasesItemModels @@ -65,7 +66,7 @@ class ReleasesFilteredItemModel: public RepoFilteredItemModel, public ReleasesIt Q_OBJECT public: - explicit ReleasesFilteredItemModel(ReleasesRawItemModel * releasesRawItemModel); + explicit ReleasesFilteredItemModel(QObject * parentRepo, ReleasesRawItemModel * releasesRawItemModel); virtual ~ReleasesFilteredItemModel() {} protected: @@ -74,6 +75,9 @@ class ReleasesFilteredItemModel: public RepoFilteredItemModel, public ReleasesIt const int channelLatestId() const; void setChannel(const int channel); const QString version(const int id) const; + + private: + QObject *m_parentRepo; // cannot use Repo class from repo.h as compile error due to cyclic headers }; class RepoReleases : public QObject, public RepoMetaData @@ -81,10 +85,9 @@ class RepoReleases : public QObject, public RepoMetaData Q_OBJECT public: - explicit RepoReleases(QObject * parent, UpdateStatus * status, UpdateNetwork * network); + explicit RepoReleases(QObject * parentRepo, UpdateStatus * status, UpdateNetwork * network); virtual ~RepoReleases() {} - void init(const QString & repoPath, const QString & nightly, const int resultsPerPage) override; int setId(const int id) override; bool retrieveMetaDataAll() override; bool retrieveMetaDataOne(const int id) override { return false; } // not implemented @@ -99,6 +102,7 @@ class RepoReleases : public QObject, public RepoMetaData private: ReleasesRawItemModel* const m_rawItemModel; ReleasesFilteredItemModel* const m_filteredItemModel; + QObject *m_parentRepo; // cannot use Repo class from repo.h as compile error due to cyclic headers void dumpItemModel(const QString modelName, const QAbstractItemModel * itemModel) const override; }; diff --git a/companion/src/updates/repotypes.h b/companion/src/updates/repotypes.h new file mode 100644 index 00000000000..3e282bd0ddb --- /dev/null +++ b/companion/src/updates/repotypes.h @@ -0,0 +1,23 @@ +/* + * 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 "repobuild.h" +#include "repogithub.h" diff --git a/companion/src/updates/updatecloudbuild.cpp b/companion/src/updates/updatecloudbuild.cpp new file mode 100644 index 00000000000..0d35670f43c --- /dev/null +++ b/companion/src/updates/updatecloudbuild.cpp @@ -0,0 +1,465 @@ +/* + * 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 "updatecloudbuild.h" +#include "repo.h" +#include "appdata.h" +#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"}; + +UpdateCloudBuild::UpdateCloudBuild(QWidget * parent) : + UpdateInterface(parent, CID_CloudBuild, tr("CloudBuild"), Repo::REPO_TYPE_BUILD, + QString("https://cloudbuild.edgetx.org/api"), "nightly"), + m_objBody(nullptr), + m_docResp(nullptr) +{ + init(); // call after UpdateInterface ctor due to virtual functions +} + +UpdateCloudBuild::~UpdateCloudBuild() +{ + if (m_objBody) + delete m_objBody; + if (m_docResp) + delete m_docResp; +} + +void UpdateCloudBuild::assetSettingsInit() +{ + if (!isSettingsIndexValid()) + return; + + g.component[id()].initAllAssets(); + + ComponentAssetData &cad = g.component[id()].asset[0]; + + cad.desc("binary"); + cad.processes((UPDFLG_Common_Asset | UPDFLG_AsyncInstall | UPDFLG_Build) &~ UPDFLG_Decompress); + cad.flags(cad.processes() | UPDFLG_CopyFiles | UPDFLG_Locked); + cad.filterType(UpdateParameters::UFT_Exact); + cad.filter("%FWFLAVOUR%"); + cad.destSubDir("FIRMWARE"); + cad.copyFilterType(UpdateParameters::UFT_Pattern); + cad.copyFilter("^%FWFLAVOUR%-%LANGUAGE%.*-%RELEASE%\\.bin$"); + cad.maxExpected(1); + + qDebug() << "Asset settings initialised"; +} + +bool UpdateCloudBuild::addBuildLanguage(const QString & flag, const QJsonArray & values, QJsonArray & buildFlags) +{ + QString val; + + if (values.contains(params()->language.toLower())) + val = params()->language.toLower(); + else if (values.contains(params()->language.toUpper())) + val = params()->language.toUpper(); + else { + status()->reportProgress(tr("Radio profile language '%1' not supported").arg(params()->language), QtCriticalMsg); + return false; + } + + addBuildFlag(buildFlags, flag, val); + + return true; +} + +bool UpdateCloudBuild::addBuildFai(const QString & flag, const QJsonArray & values, QJsonArray & buildFlags) +{ + if (!m_profileOpts.contains("faichoice") && !m_profileOpts.contains("faimode")) + return true; + + if (!values.contains("CHOICE") && !values.contains("YES")) { + status()->reportProgress(tr("fai_mode values do not contain CHOICE and YES"), QtCriticalMsg); + return false; + } + + QString val; + + if (m_profileOpts.contains("faichoice")) + val = "CHOICE"; + else if (m_profileOpts.contains("faimode")) + val = "YES"; + + m_buildFlags.append(QString("-%1").arg("fai")); + addBuildFlag(buildFlags, flag, val); + + return true; +} + +void UpdateCloudBuild::addBuildFlag(QJsonArray & buildFlags, const QString & flag, const QString & val) +{ + QJsonObject bldFlag; + bldFlag.insert("name", flag); + bldFlag.insert("value", val); + buildFlags.append(QJsonValue(bldFlag)); +} + +bool UpdateCloudBuild::arrayExists(const QJsonObject & parent, const QString child) +{ + return !parent.value(child).isUndefined() && parent.value(child).isArray(); +} + +int UpdateCloudBuild::asyncInstall() +{ + status()->reportProgress(tr("Write firmware to radio: %1").arg(g.currentProfile().burnFirmware() ? tr("true") : tr("false")), QtDebugMsg); + + if (!g.currentProfile().burnFirmware()) + return true; + + status()->progressMessage(tr("Install")); + + repo()->assets()->setFilterFlags(UPDFLG_AsyncInstall); + status()->reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_AsyncInstall)).arg(repo()->assets()->count()), QtDebugMsg); + + if (repo()->assets()->count() < 1) + return true; + + if (repo()->assets()->count() != params()->assets.at(0).maxExpected) { + status()->reportProgress(tr("Expected %1 asset for install but %2 found").arg(params()->assets.at(0).maxExpected).arg(repo()->assets()->count()), QtCriticalMsg); + return false; + } + + QString destPath = updateDir(); + + if (!repo()->assets()->subDirectory().isEmpty()) + destPath.append("/" % repo()->assets()->subDirectory()); + + const UpdateParameters::AssetParams &ap = params()->assets.at(0); + + QString pattern(params()->buildFilterPattern(ap.copyFilterType, ap.copyFilter)); + QRegularExpression filter(pattern, QRegularExpression::CaseInsensitiveOption); + + QDirIterator it(destPath); + + bool found = false; + + while (it.hasNext()) + { + QFileInfo file(it.next()); + + if ((!filter.pattern().isEmpty()) && (!file.fileName().contains(filter))) + continue; + + destPath.append("/" % file.fileName()); + found = true; + break; + } + + if (!found) { + status()->reportProgress(tr("Firmware not found in %1 using filter %2").arg(destPath).arg(filter.pattern()), QtCriticalMsg); + return false; + } + + g.currentProfile().fwName(destPath); + + int ret = QMessageBox::question(status()->progress(), CPN_STR_APP_NAME, tr("Write the updated firmware to the radio now ?"), QMessageBox::Yes | QMessageBox::No); + if (ret == QMessageBox::Yes) { + FlashFirmwareDialog *dlg = new FlashFirmwareDialog(this); + dlg->exec(); + dlg->deleteLater(); + } + + return true; +} + +bool UpdateCloudBuild::buildFlaggedAsset(const int row) +{ + m_buildFlags.clear(); + m_jobStatus.clear(); + m_profileOpts.clear(); + m_radio = repo()->assets()->name(row); + + status()->reportProgress(tr("Building firmware for: %1").arg(m_radio)); + status()->progressMessage(tr("Building firmware for: %1").arg(m_radio)); + + if (params()->logLevel == QtDebugMsg) { + m_logDir = QString("%1/logs").arg(downloadDir()); + + if (!QDir().mkpath(m_logDir)) { + status()->reportProgress(tr("Failed to create directory %1!").arg(m_logDir), QtCriticalMsg); + return false; + } + } + + Firmware *fw = getCurrentFirmware(); + m_profileOpts = fw->getId().split("-"); + // [0] = edgetx + // [1] = radio id + // [last] = language code + + QJsonObject target; + + if (params()->logLevel == QtDebugMsg) + network()->saveJsonObjToFile(*repo()->config(), QString("%1/targets.txt").arg(m_logDir)); + + if (!objectExists(*repo()->config(), "targets")) { + status()->reportProgress(tr("Unexpected format for build targets meta data"), QtCriticalMsg); + return false; + } + + const QJsonObject &targets = repo()->config()->value("targets").toObject(); + target = targets.value(m_radio).toObject(); + + if (target.isEmpty()) { + status()->reportProgress(tr("No build support for target %1").arg(m_radio), QtCriticalMsg); + return false; + } + + if (params()->logLevel == QtDebugMsg) + network()->saveJsonObjToFile(target, QString("%1/%2_target.txt").arg(m_logDir).arg(m_radio)); + + QJsonArray targetTags = target.value("tags").toArray(); + + if (targetTags.isEmpty()) { + status()->reportProgress(tr("Build target %1 has no valid tags").arg(m_radio), QtWarningMsg); + } + + const QJsonObject flags = repo()->config()->value("flags").toObject(); + + if (flags.isEmpty()) { + status()->reportProgress(tr("No flag entries found"), QtCriticalMsg); + return false; + } + + QJsonArray arrBuildFlags; + + QStringList flagKeys = flags.keys(); + + for (auto i = flagKeys.cbegin(), end = flagKeys.cend(); i != end; ++i) { + const QJsonObject &flag = flags.value(*i).toObject(); + const QJsonArray &fvals = flag.value("values").toArray(); + + if (*i == "language") { + if (!addBuildLanguage(*i, fvals, arrBuildFlags)) + return false; + } + else if (*i == "fai_mode") { + if (!addBuildFai(*i, fvals, arrBuildFlags)) + return false; + } + + //================================================================================== + // TODO flags more to be added including custom hardware target build eg SPACEMOUSE + //================================================================================== + + } + + if (m_objBody) + delete m_objBody; + + m_objBody = new QJsonObject(); + m_objBody->insert("release", QJsonValue(params()->releaseUpdate)); + m_objBody->insert("target", QJsonValue(m_radio)); + m_objBody->insert("flags", QJsonValue(arrBuildFlags)); + + QJsonDocument *docBody = new QJsonDocument(*m_objBody); + + if (m_docResp) + delete m_docResp; + + m_docResp = new QJsonDocument(); + + m_buildStartTime = QTime::currentTime(); + + 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); + + if (params()->logLevel == QtDebugMsg) + network()->saveBufferToFile(QString("%1/%2_buffer.txt").arg(m_logDir).arg(m_radio)); + + if (!network()->isSuccess()) { + status()->reportProgress(tr("Failed to initiate build job"), QtCriticalMsg); + return false; + } + + if (!m_docResp->isObject()) { + status()->reportProgress(tr("Unexpected response format when submitting build job"), QtCriticalMsg); + return false; + } + + if (params()->logLevel == QtDebugMsg) + network()->saveJsonDocToFile(m_docResp, QString("%1/%2_response.txt").arg(m_logDir).arg(m_radio)); + + const QJsonObject &obj = m_docResp->object(); + + if (stringExists(obj, "error")) { + status()->reportProgress(tr("Build error: %1").arg(obj.value("error").toString()), QtCriticalMsg); + return false; + } + else if (obj.value("target").toString() != m_radio || obj.value("release").toString() != params()->releaseUpdate) { + status()->reportProgress(tr("Process status not returned when submitting build job"), QtCriticalMsg); + return false; + } + else { + m_jobStatus = obj.value("status").toString(QString(STATUS_UNKNOWN)); + } + + if (isStatusInProgress()) + waitForBuildFinish(); + + status()->reportProgress(tr("Firmware build finished status: %1").arg(m_jobStatus)); + + if (m_jobStatus == QString(STATUS_BUILD_SUCCESS) && setAssetDownload()) + return true; + + return false; +} + +void UpdateCloudBuild::cancel() +{ + status()->reportProgress(tr("Build firmware cancelled"), QtWarningMsg); + cleanup(); +} + +void UpdateCloudBuild::checkStatus() +{ + if (m_buildStartTime.secsTo(QTime::currentTime()) > 180) { + status()->reportProgress(tr("Build firmware timeout"), QtWarningMsg); + cleanup(); + } + + getStatus(); + + if (!isStatusInProgress()) + cleanup(); + +} + +void UpdateCloudBuild::cleanup() +{ + if (m_timer.isActive()) + m_timer.stop(); + + if (m_eventLoop.isRunning()) + m_eventLoop.quit(); +} + +bool UpdateCloudBuild::getStatus() +{ + m_jobStatus = QString(STATUS_UNKNOWN); + QJsonDocument *docBody = new QJsonDocument(*m_objBody); + + if (m_docResp) + delete m_docResp; + + m_docResp = new QJsonDocument(); + network()->submitRequest(tr("Submit get build status"), repo()->urlStatus(), docBody, m_docResp); + + if (params()->logLevel == QtDebugMsg) + 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)); + + if (m_jobStatus == QString(STATUS_UNKNOWN)) { + status()->reportProgress(tr("Build status unknown"), QtCriticalMsg); + return false; + } + + return true; +} + +bool UpdateCloudBuild::isStatusInProgress() +{ + if (m_jobStatus == QString(STATUS_WAITING_FOR_BUILD) || m_jobStatus == QString(STATUS_BUILD_IN_PROGRESS)) + return true; + + return false; +} + +bool UpdateCloudBuild::objectExists(const QJsonObject & parent, const QString child) +{ + return !parent.value(child).isUndefined() && parent.value(child).isObject(); +} + +bool UpdateCloudBuild::setAssetDownload() +{ + // this format MUST align with the asset copy filter + QString name = QString("%1-%2%3-%4.bin").arg(m_radio).arg(params()->language.toLower()).arg(m_buildFlags/* has a leading hyphen*/).arg(repo()->releases()->name()); + + repo()->assets()->setDownloadName(name.toLower()); + + const QJsonObject &obj = m_docResp->object(); + + if (arrayExists(obj, "artifacts")) { + const QJsonArray &artifacts = obj.value("artifacts").toArray(); + if (artifacts.count() > 1) { + status()->reportProgress(tr("Build status contains %1 artifacts when only 1 expected").arg(artifacts.count()), QtWarningMsg); + } + for (int i = 0; i < artifacts.count(); i++) { + if (artifacts[i].isObject()) { + const QJsonObject &artifact = artifacts[i].toObject(); + if (stringExists(artifact, "slug") && artifact.value("slug").toString() == "firmware") { + if (stringExists(artifact, "download_url")) { + repo()->assets()->setDownloadUrl(artifact.value("download_url").toString()); + } + else { + status()->reportProgress(tr("Build status does not contain download url"), QtCriticalMsg); + return false; + } + } + else { + status()->reportProgress(tr("Build status does not contain firmware artifact"), QtCriticalMsg); + return false; + } + } + else { + status()->reportProgress(tr("Build status firmware artifact not in expected format"), QtCriticalMsg); + return false; + } + } + } + else { + status()->reportProgress(tr("Build status does not contain artifacts"), QtCriticalMsg); + return false; + } + + return true; +} + +bool UpdateCloudBuild::stringExists(const QJsonObject & parent, const QString child) +{ + return !parent.value(child).isUndefined() && parent.value(child).isString(); +} + +void UpdateCloudBuild::waitForBuildFinish() +{ + status()->progressMessage(tr("Waiting for firmware build to finish...")); + m_timer.setInterval(2000); // check status every 2 seconds + + connect(this, &UpdateInterface::stop, 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); +} diff --git a/companion/src/updates/updatecloudbuild.h b/companion/src/updates/updatecloudbuild.h new file mode 100644 index 00000000000..a63dbcc2e35 --- /dev/null +++ b/companion/src/updates/updatecloudbuild.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#pragma once + +#include "updateinterface.h" + +#include +#include + +class UpdateCloudBuild: public UpdateInterface +{ + Q_DECLARE_TR_FUNCTIONS(UpdateCloudBuild) + + public: + + explicit UpdateCloudBuild(QWidget * parent); + virtual ~UpdateCloudBuild(); + + protected: + int asyncInstall() override; + void assetSettingsInit() override; + bool buildFlaggedAsset(const int row) override; + + private slots: + void cancel(); + void checkStatus(); + + private: + QJsonObject *m_objBody; + QJsonDocument *m_docResp; + QEventLoop m_eventLoop; + QTimer m_timer; + + QString m_logDir; + QString m_buildFlags; + QTime m_buildStartTime; + QString m_jobStatus; + QStringList m_profileOpts; + QString m_radio; + + bool arrayExists(const QJsonObject & parent, const QString child); + bool objectExists(const QJsonObject & parent, const QString child); + bool stringExists(const QJsonObject & parent, const QString child); + + bool addBuildLanguage(const QString & flag, const QJsonArray & values, QJsonArray & buildFlags); + bool addBuildFai(const QString & flag, const QJsonArray & values, QJsonArray & buildFlags); + void addBuildFlag(QJsonArray & buildFlags, const QString & flag, const QString & val); + void cleanup(); + bool getStatus(); + bool isStatusInProgress(); + bool setAssetDownload(); + void waitForBuildFinish(); +}; diff --git a/companion/src/updates/updatecompanion.cpp b/companion/src/updates/updatecompanion.cpp index 712c7486a90..f50698874cd 100644 --- a/companion/src/updates/updatecompanion.cpp +++ b/companion/src/updates/updatecompanion.cpp @@ -53,9 +53,10 @@ #endif UpdateCompanion::UpdateCompanion(QWidget * parent) : - UpdateInterface(parent, CID_Companion, QCoreApplication::translate("UpdateCompanion", "Companion")) + UpdateInterface(parent, CID_Companion, QCoreApplication::translate("UpdateCompanion", "Companion"), Repo::REPO_TYPE_GITHUB, + QString(GH_API_REPOS_EDGETX).append("/edgetx"), "nightly") { - init(QString(GH_API_REPOS_EDGETX).append("/edgetx"), "nightly"); + init(); // call after UpdateInterface ctor due to virtual functions } void UpdateCompanion::assetSettingsInit() @@ -77,7 +78,7 @@ void UpdateCompanion::assetSettingsInit() qDebug() << "Asset settings initialised"; } -bool UpdateCompanion::asyncInstall() +int UpdateCompanion::asyncInstall() { #ifdef OS_SUPPORTED_INSTALLER //status()->reportProgress(tr("Run application installer: %1").arg(g.runAppInstaller() ? tr("true") : tr("false")), QtDebugMsg); diff --git a/companion/src/updates/updatecompanion.h b/companion/src/updates/updatecompanion.h index bfc0bae3298..3279fc5cf91 100644 --- a/companion/src/updates/updatecompanion.h +++ b/companion/src/updates/updatecompanion.h @@ -38,6 +38,6 @@ class UpdateCompanion : public UpdateInterface virtual const bool isUpdateAvailable() override; protected: - virtual bool asyncInstall() override; + virtual int asyncInstall() override; virtual void assetSettingsInit() override; }; diff --git a/companion/src/updates/updatefactories.cpp b/companion/src/updates/updatefactories.cpp index b708bafedcc..0d477893a1a 100644 --- a/companion/src/updates/updatefactories.cpp +++ b/companion/src/updates/updatefactories.cpp @@ -26,6 +26,7 @@ #include "updatesounds.h" #include "updatethemes.h" #include "updatemultiprotocol.h" +#include "updatecloudbuild.h" UpdateFactories::UpdateFactories(QWidget * parent) : QWidget(parent) @@ -64,6 +65,13 @@ const bool UpdateFactories::isUpdateAvailable(QMap & list) return ret; } +void UpdateFactories::radioProfileChanged() +{ + foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { + factory->instance()->radioProfileChanged(); + } +} + void UpdateFactories::registerUpdateFactory(UpdateFactoryInterface * factory) { foreach (UpdateFactoryInterface * registeredFactory, registeredUpdateFactories) { @@ -85,6 +93,7 @@ void UpdateFactories::registerUpdateFactories() registerUpdateFactory(new UpdateFactory(this)); registerUpdateFactory(new UpdateFactory(this)); registerUpdateFactory(new UpdateFactory(this)); + registerUpdateFactory(new UpdateFactory(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(this)); diff --git a/companion/src/updates/updatefactories.h b/companion/src/updates/updatefactories.h index ab316fd8b15..9ae9b5d37ab 100644 --- a/companion/src/updates/updatefactories.h +++ b/companion/src/updates/updatefactories.h @@ -64,6 +64,7 @@ class UpdateFactories : public QWidget UpdateInterface* const instance(const int id); const bool isUpdateAvailable(QMap & names); + void radioProfileChanged(); void registerUpdateFactories(); void registerUpdateFactory(UpdateFactoryInterface * factory); void resetAllEnvironments(); diff --git a/companion/src/updates/updatefirmware.cpp b/companion/src/updates/updatefirmware.cpp index 2a1f4493b17..5215a1b0aa1 100644 --- a/companion/src/updates/updatefirmware.cpp +++ b/companion/src/updates/updatefirmware.cpp @@ -23,9 +23,10 @@ #include "flashfirmwaredialog.h" UpdateFirmware::UpdateFirmware(QWidget * parent) : - UpdateInterface(parent, CID_Firmware, tr("Firmware")) + UpdateInterface(parent, CID_Firmware, tr("Firmware"), Repo::REPO_TYPE_GITHUB, + QString(GH_API_REPOS_EDGETX).append("/edgetx"), "nightly") { - init(QString(GH_API_REPOS_EDGETX).append("/edgetx"), "nightly"); + init(); // call after UpdateInterface ctor due to virtual functions } void UpdateFirmware::assetSettingsInit() @@ -50,9 +51,9 @@ void UpdateFirmware::assetSettingsInit() qDebug() << "Asset settings initialised"; } -bool UpdateFirmware::asyncInstall() +int UpdateFirmware::asyncInstall() { - //status->reportProgress(tr("Write firmware to radio: %1").arg(g.currentProfile().burnFirmware() ? tr("true") : tr("false")), QtDebugMsg); + status()->reportProgress(tr("Write firmware to radio: %1").arg(g.currentProfile().burnFirmware() ? tr("true") : tr("false")), QtDebugMsg); if (!g.currentProfile().burnFirmware()) return true; @@ -60,7 +61,7 @@ bool UpdateFirmware::asyncInstall() status()->progressMessage(tr("Install")); repo()->assets()->setFilterFlags(UPDFLG_AsyncInstall); - //status->reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_AsyncInstall)).arg(assets->count()), QtDebugMsg); + status()->reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_AsyncInstall)).arg(repo()->assets()->count()), QtDebugMsg); if (repo()->assets()->count() < 1) return true; diff --git a/companion/src/updates/updatefirmware.h b/companion/src/updates/updatefirmware.h index 29bda02f749..a8ae8b8be80 100644 --- a/companion/src/updates/updatefirmware.h +++ b/companion/src/updates/updatefirmware.h @@ -33,6 +33,6 @@ class UpdateFirmware : public UpdateInterface virtual ~UpdateFirmware() {} protected: - virtual bool asyncInstall() override; + virtual int asyncInstall() override; virtual void assetSettingsInit() override; }; diff --git a/companion/src/updates/updateinterface.cpp b/companion/src/updates/updateinterface.cpp index 1695269ac50..a89652c5a78 100644 --- a/companion/src/updates/updateinterface.cpp +++ b/companion/src/updates/updateinterface.cpp @@ -20,13 +20,9 @@ */ #include "updateinterface.h" +#include "repobuild.h" +#include "repogithub.h" #include "helpers.h" -#include "updatefirmware.h" -#include "updatecompanion.h" -#include "updatesdcard.h" -#include "updatesounds.h" -#include "updatethemes.h" -#include "updatemultiprotocol.h" #include "minizinterface.h" #include @@ -34,17 +30,20 @@ #include #include #include +#include +#include -UpdateInterface::UpdateInterface(QWidget * parent, ComponentIdentity id, QString name) : +UpdateInterface::UpdateInterface(QWidget * parent, ComponentIdentity id, QString name, Repo::RepoType repoType, + const QString & path, const QString & nightly, const int resultsPerPage) : QWidget(parent), m_id(id), m_name(name), m_params(new UpdateParameters(this)), m_status(new UpdateStatus(this)), m_network(new UpdateNetwork(this, m_status)), - m_repo(new Repo(this, m_status, m_network)) + m_repo(repoType == Repo::REPO_TYPE_GITHUB ? static_cast(new RepoGitHub(this, m_status, m_network, path, nightly, resultsPerPage)) : + static_cast(new RepoBuild(this, m_status, m_network, path, nightly, resultsPerPage))) { - } UpdateInterface::~UpdateInterface() @@ -112,7 +111,37 @@ void UpdateInterface::assetSettingsSave() } } -bool UpdateInterface::asyncInstall() +int UpdateInterface::asyncInstall() +{ + return true; +} + +int UpdateInterface::build() +{ + m_status->progressMessage(tr("Building assets")); + + if (!buildFlaggedAssets()) { + m_status->reportProgress(tr("Unable to build flagged assets"), QtCriticalMsg); + return false; + } + + return true; +} + +bool UpdateInterface::buildFlaggedAssets() +{ + 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)) + return false; + } + + return true; +} + +bool UpdateInterface::buildFlaggedAsset(const int row) { return true; } @@ -358,7 +387,7 @@ bool UpdateInterface::copyStructure() return true; } -bool UpdateInterface::copyToDestination() +int UpdateInterface::copyToDestination() { m_status->progressMessage(tr("Copying to destination")); @@ -370,6 +399,18 @@ bool UpdateInterface::copyToDestination() return true; } +int UpdateInterface::decompress() +{ + m_status->progressMessage(tr("Decompressing assets")); + + if (!decompressFlaggedAssets()) { + m_status->reportProgress(tr("Unable to decompress flagged assets"), QtDebugMsg); + return false; + } + + return true; +} + const QString UpdateInterface::decompressDir() const { return m_decompressDir; @@ -441,31 +482,24 @@ bool UpdateInterface::downloadFlaggedAssets() 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 (!m_repo->assets()->downloadToFile(i, m_downloadDir)) + if (!downloadFlaggedAsset(i)) return false; } return true; } -bool UpdateInterface::download() +bool UpdateInterface::downloadFlaggedAsset(const int row) { - m_status->progressMessage(tr("Downloading assets")); - - if (!downloadFlaggedAssets()) { - m_status->reportProgress(tr("Unable to download flagged assets"), QtDebugMsg); - return false; - } - - return true; + return m_repo->assets()->downloadToFile(row, m_downloadDir); } -bool UpdateInterface::decompress() +int UpdateInterface::download() { - m_status->progressMessage(tr("Decompressing assets")); + m_status->progressMessage(tr("Downloading...")); - if (!decompressFlaggedAssets()) { - m_status->reportProgress(tr("Unable to decompress flagged assets"), QtDebugMsg); + if (!downloadFlaggedAssets()) { + m_status->reportProgress(tr("Unable to download flagged assets"), QtDebugMsg); return false; } @@ -504,7 +538,7 @@ bool UpdateInterface::flagAssets() return true; } -bool UpdateInterface::housekeeping() +int UpdateInterface::housekeeping() { m_status->progressMessage(tr("Housekeeping")); int cnt = 0; @@ -554,10 +588,9 @@ const int UpdateInterface::id() const return (int)m_id; } -void UpdateInterface::init(QString repoPath, QString nightly, int resultsPerPage) +void UpdateInterface::init() { appSettingsInit(); - m_repo->init(repoPath, nightly, resultsPerPage); releaseCurrent(); } @@ -632,12 +665,20 @@ UpdateNetwork* const UpdateInterface::network() const return m_network; } +void UpdateInterface::onStatusCancelled() +{ + m_status->setMaximum(0); + m_stopping = true; + emit stop(); + m_status->reportProgress(tr("Update cancelled"), QtWarningMsg); +} + UpdateParameters* const UpdateInterface::params() const { return m_params; } -bool UpdateInterface::preparation() +int UpdateInterface::preparation() { m_status->progressMessage(tr("Preparing")); int cnt = 0; @@ -678,6 +719,12 @@ bool UpdateInterface::preparation() return true; } +void UpdateInterface::radioProfileChanged() +{ + resetEnvironment(); + m_repo->releases()->invalidate(); +} + void UpdateInterface::releaseClear() { g.component[m_id].releaseClear(); @@ -698,7 +745,7 @@ const QStringList UpdateInterface::releaseList() return m_repo->releases()->list(); } -bool UpdateInterface::releaseSettingsSave() +int UpdateInterface::releaseSettingsSave() { m_status->reportProgress(tr("Save release settings"), QtDebugMsg); g.component[m_id].release(m_repo->releases()->name()); @@ -760,6 +807,62 @@ bool UpdateInterface::retrieveRepoJsonFile(const QString & filename, QJsonDocume return m_repo->getJson(filename, json); } +void UpdateInterface::runAsyncInstall() +{ + m_result = asyncInstall(); + if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 start async failed").arg(m_name), QtCriticalMsg); + emit finished(); +} + +void UpdateInterface::runBuild() +{ + m_result = build(); + if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 preparation failed").arg(m_name), QtCriticalMsg); + emit finished(); +} + +void UpdateInterface::runCopyToDestination() +{ + m_result = copyToDestination(); + if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 copy to destination failed").arg(m_name), QtCriticalMsg); + emit finished(); +} + +void UpdateInterface::runDecompress() +{ + m_result = decompress(); + if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 decompress failed").arg(m_name), QtCriticalMsg); + emit finished(); +} + +void UpdateInterface::runDownload() +{ + m_result = download(); + if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 download failed").arg(m_name), QtCriticalMsg); + emit finished(); +} + +void UpdateInterface::runHousekeeping() +{ + m_result = housekeeping(); + if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 housekeeping failed").arg(m_name), QtCriticalMsg); + emit finished(); +} + +void UpdateInterface::runPreparation() +{ + m_result = preparation(); + if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 preparation failed").arg(m_name), QtCriticalMsg); + emit finished(); +} + +void UpdateInterface::runReleaseSettingsSave() +{ + m_result = releaseSettingsSave(); + if (m_result == PROC_RESULT_FAIL) m_status->reportProgress(tr("%1 save release settings failed").arg(m_name), QtCriticalMsg); + emit finished(); +} + bool UpdateInterface::setFilteredAssets(const UpdateParameters::AssetParams & ap) { if (!filterAssets(ap)) @@ -917,51 +1020,79 @@ bool UpdateInterface::update(ProgressWidget * progress) if (!(m_params->flags & UPDFLG_Update)) return true; + m_result = PROC_RESULT_SUCCESS; + m_stopping = false; m_status->setProgress(progress); m_status->setLogLevel(m_params->logLevel); - m_status->setInfo(tr("Processing updates for: %1").arg(m_name)); m_status->setValue(0); m_status->setMaximum(100); - m_status->reportProgress(tr("Processing updates for: %1").arg(m_name), QtInfoMsg); + // the default behaviour of the dialog is to close on clicking the cancel button + // but need to keep it open to display the current process until it can be interrupted + // and then display close button + status()->keepOpen(true); - if (!preparation()) { - m_status->reportProgress(tr("%1 preparation failed").arg(m_name), QtCriticalMsg); - return false; + 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 loop will not exit + // Note: finished does not indicate the exit status + connect(this, &UpdateInterface::finished, [&]() { if (loop.isRunning()) loop.quit(); }); + + if (okToRun()) { + // 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(); } - if (!download()) { - m_status->reportProgress(tr("%1 download failed").arg(m_name), QtCriticalMsg); - return false; + if (okToRun()) { + QTimer::singleShot(100, this, &UpdateInterface::runBuild); + loop.exec(); } - if (!decompress()) { - m_status->reportProgress(tr("%1 decompress failed").arg(m_name), QtCriticalMsg); - return false; + if (okToRun()) { + QTimer::singleShot(100, this, &UpdateInterface::runDownload); + loop.exec(); } - if (!copyToDestination()) { - m_status->reportProgress(tr("%1 copy to destination failed").arg(m_name), QtCriticalMsg); - return false; + if (okToRun()) { + QTimer::singleShot(100, this, &UpdateInterface::runDecompress); + loop.exec(); + } + + if (okToRun()) { + QTimer::singleShot(100, this, &UpdateInterface::runCopyToDestination); + loop.exec(); } // perform before async install in case Companion restarted - if (!releaseSettingsSave()) { - m_status->reportProgress(tr("Failed to save release settings"), QtDebugMsg); - return false; + if (okToRun()) { + QTimer::singleShot(100, this, &UpdateInterface::runReleaseSettingsSave); + loop.exec(); } - if (!asyncInstall()) { - m_status->reportProgress(tr("%1 start async failed").arg(m_name), QtCriticalMsg); - return false; + if (okToRun()) { + QTimer::singleShot(100, this, &UpdateInterface::runAsyncInstall); + loop.exec(); } - if (!housekeeping()) { - m_status->reportProgress(tr("%1 housekeeping failed").arg(m_name), QtCriticalMsg); - return false; + if (okToRun()) { + QTimer::singleShot(100, this, &UpdateInterface::runHousekeeping); + loop.exec(); } + disconnect(m_status, &UpdateStatus::cancelled, this, &UpdateInterface::onStatusCancelled); + + // allow the dialog to be closed by clicking the close button + status()->keepOpen(false); + + if (!okToRun()) + return false; + m_status->reportProgress(tr("%1 update successful").arg(m_name), QtInfoMsg); if (!m_status->progress()) @@ -1004,6 +1135,8 @@ const QString UpdateInterface::updateFlagToString(const int flag) return "Locked"; case UPDFLG_Preparation: return "Preparation"; + case UPDFLG_Build: + return "Build"; case UPDFLG_Download: return "Download"; case UPDFLG_Decompress: diff --git a/companion/src/updates/updateinterface.h b/companion/src/updates/updateinterface.h index 5d6421c9c32..746a70ce69e 100644 --- a/companion/src/updates/updateinterface.h +++ b/companion/src/updates/updateinterface.h @@ -48,6 +48,7 @@ class UpdateInterface : public QWidget CID_Themes = 3, CID_MultiProtocol = 4, CID_Companion = 5, + CID_CloudBuild = 6, }; Q_ENUM(ComponentIdentity) @@ -65,12 +66,23 @@ class UpdateInterface : public QWidget UPDFLG_AsyncInstall = 1 << 10, UPDFLG_DelDownloads = 1 << 11, UPDFLG_DelDecompress = 1 << 12, + UPDFLG_Build = 1 << 13, UPDFLG_Common_Asset = UPDFLG_Download | UPDFLG_Decompress | UPDFLG_CopyDest, UPDFLG_Common = UPDFLG_Common_Asset | UPDFLG_Preparation | UPDFLG_Housekeeping, }; Q_ENUM(UpdateFlags) - explicit UpdateInterface(QWidget * parent, ComponentIdentity id, QString name); + enum ProcessResult { + PROC_RESULT_FAIL, + PROC_RESULT_SUCCESS, + PROC_RESULT_CANCELLED, + }; + Q_ENUM(ProcessResult) + + typedef bool (*processFunc)(); + + explicit UpdateInterface(QWidget * parent, ComponentIdentity id, QString name, Repo::RepoType repoType, + const QString & path, const QString & nightly = QString(), const int resultsPerPage= -1); virtual ~UpdateInterface(); virtual void assetSettingsSave(); @@ -80,31 +92,40 @@ class UpdateInterface : public QWidget virtual const QString releaseCurrent(); virtual const QString releaseLatest(); virtual const QString releaseUpdate(); - virtual bool update(ProgressWidget * progress = nullptr); virtual const QString versionCurrent(); const int id() const; const bool isUpdateable() const; const QString name() const; UpdateParameters* const params() const; + void radioProfileChanged(); void releaseClear(); const QStringList releaseList(); void resetEnvironment(); void setReleaseChannel(const int channel); void setRunUpdate(); + bool update(ProgressWidget * progress = nullptr); + + signals: + void stop(); + void finished(); protected: virtual void assetSettingsInit() = 0; virtual void assetSettingsLoad(); - virtual bool asyncInstall(); + virtual int asyncInstall(); + virtual int build(); + virtual bool buildFlaggedAsset(const int row); virtual bool copyAsset(); - virtual bool copyToDestination(); - virtual bool decompress(); - virtual bool download(); + virtual int copyToDestination(); + virtual int decompress(); + virtual int download(); + virtual bool downloadFlaggedAsset(const int row); virtual bool flagAssets(); - virtual bool housekeeping(); - virtual bool preparation(); + virtual int housekeeping(); + virtual int preparation(); + bool buildFlaggedAssets(); bool copyFiles(); bool copyFlaggedAssets(); bool copyStructure(); @@ -114,7 +135,7 @@ class UpdateInterface : public QWidget const QString downloadDir() const; bool downloadFlaggedAssets(); bool filterAssets(const UpdateParameters::AssetParams & ap); - void init(QString repo, QString nightly = "", int resultsPerPage = -1); + void init(); const bool isSettingsIndexValid() const; UpdateNetwork* const network() const; Repo* const repo() const; @@ -126,12 +147,16 @@ class UpdateInterface : public QWidget void setParamFolders(); void setReleaseId(QString name); UpdateStatus* const status() const; + const bool isStopping() const { return m_stopping; } const QString updateDir() const; static QStringList versionToStringList(QString version); static const QString updateFlagsToString(const int flags); static const QString updateFlagToString(const int flag); + private slots: + void onStatusCancelled(); + private: const ComponentIdentity m_id; const QString m_name; @@ -142,10 +167,22 @@ class UpdateInterface : public QWidget QString m_downloadDir; QString m_decompressDir; QString m_updateDir; + bool m_stopping; + int m_result; void appSettingsInit(); bool checkCreateDirectory(const QString & dirSetting, const UpdateFlags flag); bool decompressArchive(const QString & archivePath, const QString & destPath); - bool releaseSettingsSave(); + int releaseSettingsSave(); bool setRunFolders(); + + bool okToRun() { return m_result == PROC_RESULT_SUCCESS && m_stopping == false; } + void runAsyncInstall(); + void runBuild(); + void runCopyToDestination(); + void runDecompress(); + void runDownload(); + void runHousekeeping(); + void runPreparation(); + void runReleaseSettingsSave(); }; diff --git a/companion/src/updates/updatemultiprotocol.cpp b/companion/src/updates/updatemultiprotocol.cpp index 9c1fcd4bcd3..0c3a4ab7135 100644 --- a/companion/src/updates/updatemultiprotocol.cpp +++ b/companion/src/updates/updatemultiprotocol.cpp @@ -22,10 +22,10 @@ #include "updatemultiprotocol.h" UpdateMultiProtocol::UpdateMultiProtocol(QWidget * parent) : - UpdateInterface(parent, CID_MultiProtocol, tr("Multiprotocol")) + UpdateInterface(parent, CID_MultiProtocol, tr("Multiprotocol"), Repo::REPO_TYPE_GITHUB, + QString(GH_API_REPOS).append("/pascallanger/DIY-Multiprotocol-TX-Module"), "", 100) { - // GitHub REST API default ResultsPerPage = 30 - init(QString(GH_API_REPOS).append("/pascallanger/DIY-Multiprotocol-TX-Module"), "", 100); + init(); // call after UpdateInterface ctor due to virtual functions } void UpdateMultiProtocol::assetSettingsInit() diff --git a/companion/src/updates/updatemultiprotocol.h b/companion/src/updates/updatemultiprotocol.h index ae98748163c..6b6a5d8e496 100644 --- a/companion/src/updates/updatemultiprotocol.h +++ b/companion/src/updates/updatemultiprotocol.h @@ -21,8 +21,6 @@ #pragma once -#pragma once - #include "updateinterface.h" class UpdateMultiProtocol : public UpdateInterface diff --git a/companion/src/updates/updatenetwork.cpp b/companion/src/updates/updatenetwork.cpp index ef9d1fbfa01..baaf6ca430f 100644 --- a/companion/src/updates/updatenetwork.cpp +++ b/companion/src/updates/updatenetwork.cpp @@ -49,47 +49,68 @@ UpdateNetwork::~UpdateNetwork() delete m_file; } +void UpdateNetwork::convertBufferToJson(QJsonDocument * json) +{ + m_success = false; + QJsonParseError res; + + *json = QJsonDocument::fromJson(*m_buffer, &res); + + if (res.error || json->isNull()) { + m_status->reportProgress(tr("Unable to convert downloaded metadata to json. Error:%1\n%2").arg(res.error).arg(res.errorString()), QtCriticalMsg); + return; + } + + m_success = true; +} + // static QString UpdateNetwork::downloadDataTypeToString(const DownloadDataType val) { switch ((int)val) { - case DDT_Binary: - return "Binary"; - case DDT_Content: - return "Content"; - case DDT_MetaData: - return "Meta Data"; - case DDT_Raw: - return "Raw"; + case DDT_GitHub_Binary: + return "GitHub Binary"; + case DDT_GitHub_Content: + return "GitHub Content"; + case DDT_GitHub_MetaData: + return "GitHub Meta Data"; + case DDT_GitHub_Raw: + return "GitHub Raw"; + case DDT_GitHub_SaveToFile: + return "GitHub Save to file"; + case DDT_Build_MetaData: + return "Build Meta Data"; + case DDT_Build_SaveToFile: + return "Build Save to file"; default: return CPN_STR_UNKNOWN_ITEM; } } -void UpdateNetwork::downloadMetaData(const QString & url, QJsonDocument * json) +void UpdateNetwork::downloadJson(const QString & url, QJsonDocument * json) { - downloadToBuffer(DDT_MetaData, url); + downloadToBuffer(DDT_GitHub_Content, url); if (m_success) convertBufferToJson(json); } -void UpdateNetwork::downloadJson(const QString & url, QJsonDocument * json) +void UpdateNetwork::downloadJsonAsset(const QString & url, QJsonDocument * json) { - downloadToBuffer(DDT_Content, url); + downloadToBuffer(DDT_GitHub_Content, url); if (m_success) convertBufferToJson(json); } -void UpdateNetwork::downloadJsonAsset(const QString & url, QJsonDocument * json) +void UpdateNetwork::downloadJsonContent(const QString & url, QJsonDocument * json) { - downloadToBuffer(DDT_Content, url); + downloadToBuffer(DDT_GitHub_Raw, url); if (m_success) convertBufferToJson(json); } -void UpdateNetwork::downloadJsonContent(const QString & url, QJsonDocument * json) +void UpdateNetwork::downloadMetaData(const DownloadDataType type, const QString & url, QJsonDocument * json) { - downloadToBuffer(DDT_Raw, url); + downloadToBuffer(type, url); if (m_success) convertBufferToJson(json); } @@ -99,12 +120,12 @@ void UpdateNetwork::downloadToBuffer(const DownloadDataType type, const QString m_success = false; download(type, url, - type == DDT_MetaData ? GH_ACCEPT_HEADER_METADATA : - type == DDT_Raw ? GH_ACCEPT_HEADER_RAW : GH_ACCEPT_HEADER_CONTENT, + type == DDT_GitHub_MetaData ? GH_ACCEPT_HEADER_METADATA : + type == DDT_GitHub_Raw ? GH_ACCEPT_HEADER_RAW : GH_ACCEPT_HEADER_CONTENT, QString()); } -void UpdateNetwork::downloadToFile(const QString & url, const QString & filePath) +void UpdateNetwork::downloadToFile(const DownloadDataType type, const QString & url, const QString & filePath) { m_success = false; @@ -135,56 +156,64 @@ void UpdateNetwork::downloadToFile(const QString & url, const QString & filePath return; } - download(DDT_SaveToFile, url, GH_ACCEPT_HEADER_CONTENT, fi.absoluteFilePath()); + download(type, + url, + type == DDT_GitHub_SaveToFile ? GH_ACCEPT_HEADER_CONTENT : NULL, + fi.absoluteFilePath()); } -void UpdateNetwork::download(const DownloadDataType type, const QString & urlStr, const char * header, const QString & filePath) +void UpdateNetwork::download(const DownloadDataType type, const QString & url, const char * acceptHeader, const QString & filePath) { m_status->setValue(0); m_status->setMaximum(100); - m_buffer->clear(); - m_success = false; + if (!init(url)) + return; - if (type == DDT_SaveToFile) { + if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) { m_file = new QFile(filePath); if (!m_file->open(QIODevice::WriteOnly)) { - m_status->criticalMsg(tr("Unable to open the download file %1 for writing. Error: %2").arg(filePath).arg(m_file->errorString())); + 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; - m_url.setUrl(urlStr); - - if (!m_url.isValid()) { - m_status->criticalMsg(tr("Invalid URL: %1").arg(urlStr)); - return; + 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)); } - else - m_status->reportProgress(tr("URL: %1").arg(urlStr), QtDebugMsg); - m_request.setUrl(m_url); - m_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork); - m_request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + // run GET inside event loop with timer interrupts to allow other events to run + QEventLoop loop; + + QTimer timer; + timer.setInterval(1000); - m_request.setRawHeader(QByteArray("X-GitHub-Api-Version"), GH_API_VERSION); - m_request.setRawHeader(QByteArray("Accept"), QByteArray(header)); + connect(&timer, &QTimer::timeout, [&]() { + m_status->reportProgress(tr("Downloading..."), QtDebugMsg); + }); m_reply = m_manager.get(m_request); connect(m_reply, QOverload::of(&QNetworkReply::errorOccurred), [=] (QNetworkReply::NetworkError code) { - // leave it to the finished slot to deal with error condition - m_status->criticalMsg(tr("Network error has occurred. Error code: %1").arg(code)); + m_success = false; + m_status->reportProgress(tr("Network error has occurred. Error code: %1").arg(code), QtCriticalMsg); }); connect(m_reply, &QNetworkReply::sslErrors, [=]() { - m_status->reportProgress(tr("Ssl library version: %1").arg(QSslSocket::sslLibraryVersionString()), QtDebugMsg); + 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_SaveToFile) { + if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) { m_file->write(m_reply->readAll()); } else { @@ -194,19 +223,19 @@ void UpdateNetwork::download(const DownloadDataType type, const QString & urlStr }); connect(m_reply, &QNetworkReply::finished, [&]() { - onDownloadFinished(m_reply, type); - }); + if (timer.isActive()) + timer.stop(); - connect(m_reply, &QNetworkReply::downloadProgress, [=](const qint64 bytesRead, const qint64 totalBytes) { - m_status->setMaximum(totalBytes); - m_status->setValue(bytesRead); - }); + if (loop.isRunning()) + loop.quit(); - while (!m_reply->isFinished()) { - qApp->processEvents(); - } + disconnect(m_reply, nullptr); + + onDownloadFinished(m_reply, type); + }); - return; + timer.start(); + loop.exec(); } QByteArray * UpdateNetwork::getDownloadBuffer() @@ -214,6 +243,27 @@ QByteArray * UpdateNetwork::getDownloadBuffer() return m_buffer; } +bool UpdateNetwork::init(const QString & urlStr) +{ + m_buffer->clear(); + m_success = false; + m_request = QNetworkRequest(); + + m_url.setUrl(urlStr); + + if (!m_url.isValid()) { + m_status->reportProgress(tr("Invalid URL: %1").arg(urlStr), QtCriticalMsg); + return false; + } + else + m_status->reportProgress(tr("URL: %1").arg(urlStr), QtDebugMsg); + + m_request.setUrl(m_url); + m_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork); + m_request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + return true; +} + const bool UpdateNetwork::isSuccess() { return m_success; @@ -223,38 +273,126 @@ void UpdateNetwork::onDownloadFinished(QNetworkReply * reply, DownloadDataType t { m_status->setValue(m_status->maximum()); - if (type == DDT_SaveToFile) { + if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) { m_file->flush(); m_file->close(); } if (m_reply->error()) { m_success = false; - m_status->criticalMsg(tr("Unable to download %1. Error:%2\n%3").arg(downloadDataTypeToString(type)).arg(m_reply->error()).arg(m_reply->errorString())); + m_status->reportProgress(tr("Unable to download %1. GET error:%2\n%3").arg(downloadDataTypeToString(type)).arg(m_reply->error()).arg(m_reply->errorString()), QtCriticalMsg); - if (type == DDT_SaveToFile) + if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) m_file->remove(); } else m_success = true; - if (type == DDT_SaveToFile) { + if (type == DDT_GitHub_SaveToFile || type == DDT_Build_SaveToFile) { delete m_file; m_file = nullptr; } } -void UpdateNetwork::convertBufferToJson(QJsonDocument * json) +void UpdateNetwork::post(const QString & action, const QString & url, QJsonDocument * data) { - m_success = false; - QJsonParseError res; - - *json = QJsonDocument::fromJson(*m_buffer, &res); + m_status->setValue(0); + m_status->setMaximum(0); // put progress bar in wait mode - if (res.error || json->isNull()) { - m_status->criticalMsg(tr("Unable to convert downloaded metadata to json. Error:%1\n%2").arg(res.error).arg(res.errorString())); + if (!init(url)) return; + + 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); + }); + + m_reply = m_manager.post(m_request, data->toJson(QJsonDocument::Compact)); + + connect(m_reply, QOverload::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::readyRead, [=]() { + 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); + + 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(); +} + +bool UpdateNetwork::saveBufferToFile(const QString & filePath) +{ + QFile f(QDir::toNativeSeparators(filePath)); + + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { + m_status->reportProgress(tr("Failed to open %1 for writing").arg(QDir::toNativeSeparators(filePath)), QtWarningMsg); + return false; } - m_success = true; + QTextStream out(&f); + out << *m_buffer; + f.close(); + + return true; +} + +bool UpdateNetwork::saveJsonDocToFile(QJsonDocument * json, const QString & filePath) +{ + QFile f(QDir::toNativeSeparators(filePath)); + + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { + m_status->reportProgress(tr("Failed to open %1 for writing").arg(QDir::toNativeSeparators(filePath)), QtWarningMsg); + return false; + } + + QTextStream out(&f); + out << json->toJson(); + f.close(); + + return true; +} + +bool UpdateNetwork::saveJsonObjToFile(QJsonObject & obj, const QString & filePath) +{ + saveJsonDocToFile(new QJsonDocument(obj), filePath); + return true; +} + +void UpdateNetwork::submitRequest(const QString & action, const QString & url, QJsonDocument * data, QJsonDocument * response) +{ + post(action, url, data); + if (m_success) + convertBufferToJson(response); } diff --git a/companion/src/updates/updatenetwork.h b/companion/src/updates/updatenetwork.h index 59fe0b2dfc8..54d3e1e2d56 100644 --- a/companion/src/updates/updatenetwork.h +++ b/companion/src/updates/updatenetwork.h @@ -35,11 +35,18 @@ class UpdateNetwork : public QObject public: enum DownloadDataType { - DDT_Binary, - DDT_Content, - DDT_SaveToFile, - DDT_MetaData, - DDT_Raw, + DDT_Unknown, + DDT_GitHub_Binary, + DDT_GitHub_First = DDT_GitHub_Binary, + DDT_GitHub_Content, + DDT_GitHub_MetaData, + DDT_GitHub_Raw, + DDT_GitHub_SaveToFile, + DDT_GitHub_Last = DDT_GitHub_SaveToFile, + DDT_Build_MetaData, + DDT_Build_First = DDT_Build_MetaData, + DDT_Build_SaveToFile, + DDT_Build_Last = DDT_Build_SaveToFile, }; Q_ENUM(DownloadDataType) @@ -47,15 +54,20 @@ class UpdateNetwork : public QObject virtual ~UpdateNetwork(); void convertBufferToJson(QJsonDocument * json); - void downloadMetaData(const QString & url, QJsonDocument * json); + void downloadMetaData(const DownloadDataType type, const QString & url, QJsonDocument * json); void downloadJson(const QString & url, QJsonDocument * json); void downloadJsonAsset(const QString & url, QJsonDocument * json); void downloadJsonContent(const QString & url, QJsonDocument * json); - void downloadToFile(const QString & url, const QString & filePath); + void downloadToFile(const DownloadDataType type, const QString & url, const QString & filePath); void downloadToBuffer(const DownloadDataType type, const QString & url); - void download(const DownloadDataType type, const QString & urlStr, const char * header, const QString & filePath); + void download(const DownloadDataType type, const QString & url, const char * acceptHeader, const QString & filePath); QByteArray * getDownloadBuffer(); const bool isSuccess(); + void post(const QString & action, const QString & url, QJsonDocument * json); + bool saveBufferToFile(const QString & filePath); + bool saveJsonDocToFile(QJsonDocument * json, const QString & filePath); + bool saveJsonObjToFile(QJsonObject & obj, const QString & filePath); + void submitRequest(const QString & action, const QString & url, QJsonDocument * data, QJsonDocument * response); static QString downloadDataTypeToString(const DownloadDataType val); @@ -72,4 +84,5 @@ class UpdateNetwork : public QObject QUrl m_url; bool m_success; + bool init(const QString & url); }; diff --git a/companion/src/updates/updateparameters.cpp b/companion/src/updates/updateparameters.cpp index 02fbeabaefc..3e8d0faf9ba 100644 --- a/companion/src/updates/updateparameters.cpp +++ b/companion/src/updates/updateparameters.cpp @@ -50,6 +50,9 @@ QString UpdateParameters::buildFilterPattern(const UpdateFilterType filterType, pattern.replace("%FWFLAVOUR%", fwFlavour); pattern.replace("%LANGUAGE%", language); + QString rel = releaseUpdate; + rel.replace(".", "\\."); + pattern.replace("%RELEASE%", rel); switch ((int)filterType) { case UFT_Exact: diff --git a/companion/src/updates/updates.cpp b/companion/src/updates/updates.cpp index 31c2a0d7e03..3d879d0c646 100644 --- a/companion/src/updates/updates.cpp +++ b/companion/src/updates/updates.cpp @@ -122,10 +122,10 @@ void Updates::runUpdate() { bool ok = false; ProgressDialog progressDialog(this, tr("Update Components"), CompanionIcon("fuses.png"), true); - progressDialog.progress()->lock(true); + progressDialog.setProcessStarted(); progressDialog.progress()->setInfo(tr("Starting...")); ok = factories->updateAll(progressDialog.progress()); - progressDialog.progress()->lock(false); + progressDialog.setProcessStopped(); progressDialog.progress()->setInfo(tr("Finished %1").arg(ok ? tr("successfully") : tr("with errors"))); progressDialog.exec(); diff --git a/companion/src/updates/updatesdcard.cpp b/companion/src/updates/updatesdcard.cpp index 9a10a4e4c5e..4139284e323 100644 --- a/companion/src/updates/updatesdcard.cpp +++ b/companion/src/updates/updatesdcard.cpp @@ -24,9 +24,10 @@ #include UpdateSDCard::UpdateSDCard(QWidget * parent) : - UpdateInterface(parent, CID_SDCard, tr("SD Card")) + UpdateInterface(parent, CID_SDCard, tr("SD Card"), Repo::REPO_TYPE_GITHUB, + QString(GH_API_REPOS_EDGETX).append("/edgetx-sdcard"), "Latest") { - init(QString(GH_API_REPOS_EDGETX).append("/edgetx-sdcard"), "Latest"); + init(); // call after UpdateInterface ctor due to virtual functions } void UpdateSDCard::assetSettingsInit() diff --git a/companion/src/updates/updatesdialog.cpp b/companion/src/updates/updatesdialog.cpp index 633c7bfca90..9157ca7487b 100644 --- a/companion/src/updates/updatesdialog.cpp +++ b/companion/src/updates/updatesdialog.cpp @@ -174,7 +174,6 @@ UpdatesDialog::UpdatesDialog(QWidget * parent, UpdateFactories * factories) : msg->setText(tr("Retrieving latest release information for %1").arg(name)); chkUpdate[i] = new QCheckBox(); chkUpdate[i]->setProperty("index", i); - chkUpdate[i]->setChecked(!iface->isReleaseLatest()); grid->addWidget(chkUpdate[i], row, col++); grid->setAlignment(chkUpdate[i], Qt::AlignHCenter); @@ -190,32 +189,35 @@ UpdatesDialog::UpdatesDialog(QWidget * parent, UpdateFactories * factories) : grid->addWidget(lblCurrentRel[i], row, col++); cboUpdateRel[i] = new QComboBox(); + cboUpdateRel[i]->setSizeAdjustPolicy(QComboBox::AdjustToContents); cboUpdateRel[i]->addItems(iface->releaseList()); grid->addWidget(cboUpdateRel[i], row, col++); connect(cboRelChannel[i], QOverload::of(&QComboBox::currentIndexChanged), [=] (const int index) { iface->setReleaseChannel(index); - chkUpdate[i]->setChecked(!iface->isReleaseLatest()); cboUpdateRel[i]->clear(); cboUpdateRel[i]->addItems(iface->releaseList()); + chkUpdate[i]->setChecked(cboUpdateRel[i]->currentIndex() != -1 && !iface->isReleaseLatest()); }); connect(cboUpdateRel[i], QOverload::of(&QComboBox::currentIndexChanged), [=] (const int index) { - chkUpdate[i]->setChecked(cboUpdateRel[i]->currentText() != lblCurrentRel[i]->text()); + chkUpdate[i]->setChecked(cboUpdateRel[i]->currentIndex() != -1 && cboUpdateRel[i]->currentText() != lblCurrentRel[i]->text()); }); btnOptions[i] = new QPushButton(tr("Options")); connect(btnOptions[i], &QPushButton::clicked, [=]() { UpdateOptionsDialog *dlg = new UpdateOptionsDialog(this, iface, i, true); connect(dlg, &UpdateOptionsDialog::changed, [=](const int i) { - chkUpdate[i]->setChecked(!iface->isReleaseLatest()); lblCurrentRel[i]->setText(iface->releaseCurrent()); cboUpdateRel[i]->setCurrentText(iface->releaseUpdate()); + chkUpdate[i]->setChecked(cboUpdateRel[i]->currentIndex() != -1 && !iface->isReleaseLatest()); }); dlg->exec(); dlg->deleteLater(); }); + chkUpdate[i]->setChecked(cboUpdateRel[i]->currentIndex() != -1 && !iface->isReleaseLatest()); + grid->addWidget(btnOptions[i], row, col++); } diff --git a/companion/src/updates/updatesounds.cpp b/companion/src/updates/updatesounds.cpp index 027bc7e34ec..33a415645c7 100644 --- a/companion/src/updates/updatesounds.cpp +++ b/companion/src/updates/updatesounds.cpp @@ -28,9 +28,10 @@ #include UpdateSounds::UpdateSounds(QWidget * parent) : - UpdateInterface(parent, CID_Sounds, tr("Sounds")) + UpdateInterface(parent, CID_Sounds, tr("Sounds"), Repo::REPO_TYPE_GITHUB, + QString(GH_API_REPOS_EDGETX).append("/edgetx-sdcard-sounds"), "latest", 50) { - init(QString(GH_API_REPOS_EDGETX).append("/edgetx-sdcard-sounds"), "", 50); + init(); // call after UpdateInterface ctor due to virtual functions langPacks = new QStandardItemModel(); } diff --git a/companion/src/updates/updatestatus.cpp b/companion/src/updates/updatestatus.cpp index a69ff0195df..0e518e5a7a0 100644 --- a/companion/src/updates/updatestatus.cpp +++ b/companion/src/updates/updatestatus.cpp @@ -42,8 +42,8 @@ void UpdateStatus::reportProgress(QString text, QtMsgType type) qDebug() << text; } - if (type == QtCriticalMsg || type == QtFatalMsg) - criticalMsg(text); + if (type == QtFatalMsg) + fatalMsg(text); } void UpdateStatus::progressMessage(QString text) @@ -54,8 +54,21 @@ void UpdateStatus::progressMessage(QString text) qDebug() << text; } -void UpdateStatus::criticalMsg(QString msg) +void UpdateStatus::fatalMsg(QString msg) { QMessageBox::critical(m_progress, tr("Update Interface"), msg); } +void UpdateStatus::setProgress(ProgressWidget * progress) +{ + m_progress = progress; + if (m_progress) { + disconnect(m_progress, &ProgressWidget::stopped, this, &UpdateStatus::onWidgetStopped); // to avoid duplicate entries in stack + connect(m_progress, &ProgressWidget::stopped, this, &UpdateStatus::onWidgetStopped); + } +} + +void UpdateStatus::onWidgetStopped() +{ + emit cancelled(); +} diff --git a/companion/src/updates/updatestatus.h b/companion/src/updates/updatestatus.h index 202ad55a717..68854ec85d5 100644 --- a/companion/src/updates/updatestatus.h +++ b/companion/src/updates/updatestatus.h @@ -40,12 +40,19 @@ class UpdateStatus : public QObject void setMaximum(int val) { if (m_progress) m_progress->setMaximum(val); } int maximum() { return m_progress ? m_progress->maximum() : 0; } - void setProgress(ProgressWidget * progress) { m_progress = progress; } + void setProgress(ProgressWidget * progress); ProgressWidget * progress() { return m_progress; } + void keepOpen(bool val) { if (m_progress) m_progress->forceKeepOpen(val); } void reportProgress(QString text, QtMsgType type = QtInfoMsg); void progressMessage(QString text); - void criticalMsg(QString msg); + void fatalMsg(QString msg); + + public slots: + void onWidgetStopped(); + + signals: + void cancelled(); private: ProgressWidget *m_progress; diff --git a/companion/src/updates/updatethemes.cpp b/companion/src/updates/updatethemes.cpp index 1c13f49373c..831fbe7162e 100644 --- a/companion/src/updates/updatethemes.cpp +++ b/companion/src/updates/updatethemes.cpp @@ -22,9 +22,10 @@ #include "updatethemes.h" UpdateThemes::UpdateThemes(QWidget * parent) : - UpdateInterface(parent, CID_Themes, tr("Themes")) + UpdateInterface(parent, CID_Themes, tr("Themes"), Repo::REPO_TYPE_GITHUB, + QString(GH_API_REPOS_EDGETX).append("/themes")) { - init(QString(GH_API_REPOS_EDGETX).append("/themes")); + init(); // call after UpdateInterface ctor due to virtual functions } void UpdateThemes::assetSettingsInit()