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()