enh(cpn): Cloudbuild support - phase 1 (#4856)

This commit is contained in:
Neil Horne
2024-05-28 10:50:02 +10:00
committed by GitHub
parent e033da999c
commit 27f53ff195
51 changed files with 2088 additions and 977 deletions
-2
View File
@@ -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()) {
+367 -381
View File
@@ -45,13 +45,32 @@
</sizepolicy>
</property>
<property name="currentIndex">
<number>1</number>
<number>0</number>
</property>
<widget class="QWidget" name="profileTab">
<attribute name="title">
<string>Radio Profile</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,0,0,0,0">
<item row="12" column="2">
<widget class="QLabel" name="lblGeneralSettings">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">General Settings Label</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="profileNameLE"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_6">
<property name="sizePolicy">
@@ -68,6 +87,250 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="5">
<widget class="Line" name="line_8">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="17" column="2">
<widget class="QCheckBox" name="burnFirmware">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Prompt to write firmware to radio after update</string>
</property>
</widget>
</item>
<item row="10" column="2">
<widget class="QLineEdit" name="profilebackupPath">
<property name="toolTip">
<string>If set it will override the application general setting</string>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QComboBox" name="langCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>General Settings</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QWidget" name="optionsWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="optionsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="horizontalSpacing">
<number>5</number>
</property>
<property name="verticalSpacing">
<number>4</number>
</property>
</layout>
</widget>
</item>
<item row="7" column="0" colspan="5">
<widget class="QWidget" name="widget_splashImage" native="true">
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="splashLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Splash Screen</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="SplashFileName">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="SplashSelect">
<property name="text">
<string>Select Image</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="imageLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>128</width>
<height>64</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>212</width>
<height>64</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="clearImageButton">
<property name="text">
<string>Clear Image</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="Line" name="line_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="10" column="3" colspan="2">
<widget class="QPushButton" name="ProfilebackupPathButton">
<property name="toolTip">
<string>The profile specific folder, if set, will override general Backup folder</string>
</property>
<property name="text">
<string>Select Folder</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="boardCB">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="14" column="2">
<widget class="QComboBox" name="externalModuleSizeCB"/>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>External Module</string>
</property>
</widget>
</item>
<item row="18" column="2">
<widget class="QCheckBox" name="chkPromptSDSync">
<property name="text">
<string>Prompt to run SD Sync after update</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Default Int. Module</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="sizePolicy">
@@ -90,32 +353,38 @@
</property>
</widget>
</item>
<item row="9" column="2">
<widget class="QLineEdit" name="sdPath">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string/>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item row="6" column="0" colspan="5">
<widget class="Line" name="splashSeparator">
<item row="8" column="0" colspan="5">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Other Settings</string>
</property>
</widget>
</item>
<item row="19" column="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item row="16" column="2">
<widget class="QComboBox" name="channelorderCB">
<property name="sizePolicy">
@@ -252,238 +521,6 @@
</item>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Default Int. Module</string>
</property>
</widget>
</item>
<item row="9" column="3" colspan="2">
<widget class="QPushButton" name="sdPathButton">
<property name="text">
<string>Select Folder</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="sdPathLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>SD Structure path</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="12" column="2">
<widget class="QLabel" name="lblGeneralSettings">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">General Settings Label</string>
</property>
</widget>
</item>
<item row="19" column="2">
<widget class="QCheckBox" name="chkPromptSDSync">
<property name="text">
<string>Prompt to run SD Sync after update</string>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="label_13">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Default Channel Order</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="13" column="2">
<widget class="QComboBox" name="defaultInternalModuleCB"/>
</item>
<item row="4" column="2">
<widget class="QComboBox" name="langCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="7" column="0" colspan="5">
<widget class="QWidget" name="widget_splashImage" native="true">
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="splashLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Splash Screen</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="SplashFileName">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="SplashSelect">
<property name="text">
<string>Select Image</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="imageLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>128</width>
<height>64</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>212</width>
<height>64</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="clearImageButton">
<property name="text">
<string>Clear Image</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="Line" name="line_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="10" column="3" colspan="2">
<widget class="QPushButton" name="ProfilebackupPathButton">
<property name="toolTip">
<string>The profile specific folder, if set, will override general Backup folder</string>
</property>
<property name="text">
<string>Select Folder</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>General Settings</string>
</property>
</widget>
</item>
<item row="20" column="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item row="15" column="2">
<widget class="QComboBox" name="stickmodeCB">
<property name="maximumSize">
@@ -538,46 +575,19 @@ Mode 4:
</item>
</widget>
</item>
<item row="5" column="2">
<widget class="QWidget" name="optionsWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="optionsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="horizontalSpacing">
<number>5</number>
</property>
<property name="verticalSpacing">
<number>4</number>
</property>
</layout>
</widget>
<item row="13" column="2">
<widget class="QComboBox" name="defaultInternalModuleCB"/>
</item>
<item row="15" column="0">
<widget class="QLabel" name="label_14">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item row="9" column="2">
<widget class="QLineEdit" name="sdPath">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Default Stick Mode</string>
<string/>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
@@ -613,8 +623,76 @@ Mode 4:
</property>
</widget>
</item>
<item row="1" column="0" colspan="5">
<widget class="Line" name="line_8">
<item row="15" column="0">
<widget class="QLabel" name="label_14">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Default Stick Mode</string>
</property>
</widget>
</item>
<item row="9" column="3" colspan="2">
<widget class="QPushButton" name="sdPathButton">
<property name="text">
<string>Select Folder</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="sdPathLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>SD Structure path</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="profilebackupPathLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>The profile specific folder, if set, will override general Backup folder</string>
</property>
<property name="text">
<string>Backup folder</string>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="label_13">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Default Channel Order</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="6" column="0" colspan="5">
<widget class="Line" name="splashSeparator">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -626,38 +704,6 @@ Mode 4:
</property>
</widget>
</item>
<item row="8" column="0" colspan="5">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Other Settings</string>
</property>
</widget>
</item>
<item row="18" column="2">
<widget class="QCheckBox" name="burnFirmware">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Prompt to write firmware to radio after update</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
@@ -677,65 +723,6 @@ Mode 4:
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="boardCB">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="profileNameLE"/>
</item>
<item row="17" column="2">
<widget class="QCheckBox" name="renameFirmware">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Append version number to firmware file name</string>
</property>
</widget>
</item>
<item row="10" column="2">
<widget class="QLineEdit" name="profilebackupPath">
<property name="toolTip">
<string>If set it will override the application general setting</string>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="profilebackupPathLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>The profile specific folder, if set, will override general Backup folder</string>
</property>
<property name="text">
<string>Backup folder</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>External Module</string>
</property>
</widget>
</item>
<item row="14" column="2">
<widget class="QComboBox" name="externalModuleSizeCB"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="applicationTab">
@@ -1642,7 +1629,6 @@ Mode 4:
<tabstop>pbackupEnable</tabstop>
<tabstop>stickmodeCB</tabstop>
<tabstop>channelorderCB</tabstop>
<tabstop>renameFirmware</tabstop>
<tabstop>burnFirmware</tabstop>
<tabstop>splashincludeCB</tabstop>
<tabstop>snapshotPath</tabstop>
+14
View File
@@ -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) {
+2
View File
@@ -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);
+1
View File
@@ -1250,6 +1250,7 @@ void MainWindow::onCurrentProfileChanged()
g.moveCurrentProfileToTop();
Firmware::setCurrentVariant(Firmware::getFirmwareForId(g.currentProfile().fwType()));
emit firmwareChanged();
updateFactories->radioProfileChanged();
QApplication::clipboard()->clear();
updateMenus();
}
+12 -3
View File
@@ -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;
}
+21 -21
View File
@@ -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 <QDialog>
@@ -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_
+7 -2
View File
@@ -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);
}
+2
View File
@@ -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();
-1
View File
@@ -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)
+3 -1
View File
@@ -3,12 +3,14 @@ project(updates)
set(${PROJECT_NAME}_NAMES
chooserdialog
downloaddialog
repo
repoassets
repobuild
repodatamodels
repogithub
repometadata
reporeleases
updatecloudbuild
updatecompanion
updatefactories
updatefirmware
-129
View File
@@ -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 <QTime>
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();
}
-60
View File
@@ -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 <QtWidgets>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QFile>
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;
};
-86
View File
@@ -1,86 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DownloadDialog</class>
<widget class="QDialog" name="DownloadDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>562</width>
<height>82</height>
</rect>
</property>
<property name="windowTitle">
<string>Downloading: </string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources>
<include location="companion.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DownloadDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DownloadDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+15 -7
View File
@@ -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);
}
+31 -2
View File
@@ -27,6 +27,7 @@
#include "repoassets.h"
#include <QtCore>
#include <QJsonObject>
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;
};
+131 -18
View File
@@ -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<RepoGitHub*>(m_parentRepo);
if (rg) {
parseJsonObjectGitHub(obj);
}
else {
RepoBuild *rb = dynamic_cast<RepoBuild*>(m_parentRepo);
if (rb) {
parseJsonObjectBuild();
}
}
}
void AssetsRawItemModel::parseJsonObjectBuild()
{
Repo *repo = static_cast<Repo*>(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<RepoGitHub*>(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<Repo*>(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<Repo*>(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<Repo*>(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<Repo*>(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);
}
+26 -3
View File
@@ -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;
+52
View File
@@ -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());
}
+50
View File
@@ -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:
};
+18 -4
View File
@@ -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)
{
+17 -5
View File
@@ -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:
+53
View File
@@ -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());
}
+50
View File
@@ -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:
};
+14 -42
View File
@@ -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 (!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;
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);
}
-10
View File
@@ -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;
+103 -24
View File
@@ -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<RepoGitHub*>(m_parentRepo);
if (rg) {
parseJsonObjectGitHub(obj);
}
else {
RepoBuild *rb = dynamic_cast<RepoBuild*>(m_parentRepo);
if (rb) {
parseJsonObjectBuild(obj);
}
}
}
void ReleasesRawItemModel::parseJsonObjectBuild(const QJsonObject & obj)
{
Repo *repo = static_cast<Repo*>(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<Repo*>(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<Repo*>(m_parentRepo);
if (repo) {
res = retrieveMetaData(repo->releasesMetaDataType(), repo->urlReleases());
m_rawItemModel->sort(0, Qt::DescendingOrder);
}
return res;
}
int RepoReleases::setId(const int id)
+11 -7
View File
@@ -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;
};
+23
View File
@@ -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"
+465
View File
@@ -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);
}
+72
View File
@@ -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 <QEventLoop>
#include <QTimer>
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();
};
+4 -3
View File
@@ -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);
+1 -1
View File
@@ -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;
};
@@ -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<QString, int> & 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<UpdateSounds>(this));
registerUpdateFactory(new UpdateFactory<UpdateThemes>(this));
registerUpdateFactory(new UpdateFactory<UpdateMultiProtocol>(this));
registerUpdateFactory(new UpdateFactory<UpdateCloudBuild>(this));
// Note: Companion must be last as its install requires the app to be closed and thus would interrupt the update loop
registerUpdateFactory(new UpdateFactory<UpdateCompanion>(this));
+1
View File
@@ -64,6 +64,7 @@ class UpdateFactories : public QWidget
UpdateInterface* const instance(const int id);
const bool isUpdateAvailable(QMap<QString, int> & names);
void radioProfileChanged();
void registerUpdateFactories();
void registerUpdateFactory(UpdateFactoryInterface * factory);
void resetAllEnvironments();
+6 -5
View File
@@ -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;
+1 -1
View File
@@ -33,6 +33,6 @@ class UpdateFirmware : public UpdateInterface
virtual ~UpdateFirmware() {}
protected:
virtual bool asyncInstall() override;
virtual int asyncInstall() override;
virtual void assetSettingsInit() override;
};
+187 -54
View File
@@ -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 <QMessageBox>
@@ -34,17 +30,20 @@
#include <QFileInfo>
#include <QRegularExpression>
#include <QValidator>
#include <QEventLoop>
#include <QTimer>
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<Repo*>(new RepoGitHub(this, m_status, m_network, path, nightly, resultsPerPage)) :
static_cast<Repo*>(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,16 +482,21 @@ 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"));
return m_repo->assets()->downloadToFile(row, m_downloadDir);
}
int UpdateInterface::download()
{
m_status->progressMessage(tr("Downloading..."));
if (!downloadFlaggedAssets()) {
m_status->reportProgress(tr("Unable to download flagged assets"), QtDebugMsg);
@@ -460,18 +506,6 @@ bool UpdateInterface::download()
return true;
}
bool UpdateInterface::decompress()
{
m_status->progressMessage(tr("Decompressing assets"));
if (!decompressFlaggedAssets()) {
m_status->reportProgress(tr("Unable to decompress flagged assets"), QtDebugMsg);
return false;
}
return true;
}
bool UpdateInterface::filterAssets(const UpdateParameters::AssetParams & ap)
{
QString pattern(m_params->buildFilterPattern(ap.filterType, ap.filter));
@@ -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:
+47 -10
View File
@@ -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();
};
@@ -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()
@@ -21,8 +21,6 @@
#pragma once
#pragma once
#include "updateinterface.h"
class UpdateMultiProtocol : public UpdateInterface
+203 -65
View File
@@ -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)
{
downloadToBuffer(DDT_MetaData, url);
if (m_success)
convertBufferToJson(json);
}
void UpdateNetwork::downloadJson(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)
{
downloadToBuffer(DDT_Content, url);
downloadToBuffer(DDT_GitHub_Content, url);
if (m_success)
convertBufferToJson(json);
}
void UpdateNetwork::downloadJsonContent(const QString & url, QJsonDocument * json)
{
downloadToBuffer(DDT_Raw, url);
downloadToBuffer(DDT_GitHub_Raw, url);
if (m_success)
convertBufferToJson(json);
}
void UpdateNetwork::downloadMetaData(const DownloadDataType type, const QString & url, QJsonDocument * json)
{
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;
m_request.setRawHeader(QByteArray("X-GitHub-Api-Version"), GH_API_VERSION);
m_request.setRawHeader(QByteArray("Accept"), QByteArray(header));
QTimer timer;
timer.setInterval(1000);
connect(&timer, &QTimer::timeout, [&]() {
m_status->reportProgress(tr("Downloading..."), QtDebugMsg);
});
m_reply = m_manager.get(m_request);
connect(m_reply, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::errorOccurred), [=] (QNetworkReply::NetworkError code) {
// 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, [&]() {
if (timer.isActive())
timer.stop();
if (loop.isRunning())
loop.quit();
disconnect(m_reply, nullptr);
onDownloadFinished(m_reply, type);
});
connect(m_reply, &QNetworkReply::downloadProgress, [=](const qint64 bytesRead, const qint64 totalBytes) {
m_status->setMaximum(totalBytes);
m_status->setValue(bytesRead);
});
while (!m_reply->isFinished()) {
qApp->processEvents();
}
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;
m_status->setValue(0);
m_status->setMaximum(0); // put progress bar in wait mode
*json = QJsonDocument::fromJson(*m_buffer, &res);
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<QNetworkReply::NetworkError>::of(&QNetworkReply::errorOccurred), [=] (QNetworkReply::NetworkError code) {
m_success = false;
m_status->reportProgress(tr("Network error has occurred. Error code: %1").arg(code), QtCriticalMsg);
});
connect(m_reply, &QNetworkReply::sslErrors, [=]() {
m_success = false;
m_status->reportProgress(tr("Ssl library version: %1").arg(QSslSocket::sslLibraryVersionString()), QtCriticalMsg);
});
connect(m_reply, &QNetworkReply::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);
}
+21 -8
View File
@@ -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);
};
@@ -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:
+2 -2
View File
@@ -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();
+3 -2
View File
@@ -24,9 +24,10 @@
#include <QMessageBox>
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()
+6 -4
View File
@@ -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<int>::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<int>::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++);
}
+3 -2
View File
@@ -28,9 +28,10 @@
#include <QAbstractItemView>
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();
}
+16 -3
View File
@@ -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();
}
+9 -2
View File
@@ -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;
+3 -2
View File
@@ -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()