diff --git a/companion/src/apppreferencesdialog.cpp b/companion/src/apppreferencesdialog.cpp index c540199a5..fbac35ca4 100644 --- a/companion/src/apppreferencesdialog.cpp +++ b/companion/src/apppreferencesdialog.cpp @@ -29,7 +29,7 @@ #endif #include "moduledata.h" #include "compounditemmodels.h" -#include "updates/updateinterface.h" +#include "updates/updatefactories.h" #include "updates/updateoptionsdialog.h" #include @@ -462,7 +462,7 @@ void AppPreferencesDialog::initSettings() btnComponentOptions[i] = new QPushButton(tr("Options")); connect(btnComponentOptions[i], &QPushButton::clicked, [=]() { - UpdateOptionsDialog *dlg = new UpdateOptionsDialog(this, factories, i, false); + UpdateOptionsDialog *dlg = new UpdateOptionsDialog(this, factories->instance(i), i, false); dlg->exec(); dlg->deleteLater(); }); diff --git a/companion/src/mainwindow.cpp b/companion/src/mainwindow.cpp index 31168770f..95e84e6fe 100644 --- a/companion/src/mainwindow.cpp +++ b/companion/src/mainwindow.cpp @@ -46,7 +46,7 @@ #include "profilechooser.h" #include "constants.h" #include "updates/updates.h" -#include "updates/updateinterface.h" +#include "updates/updatefactories.h" #include #include diff --git a/companion/src/storage/appdata.cpp b/companion/src/storage/appdata.cpp index 9a77722cb..e98c2c972 100644 --- a/companion/src/storage/appdata.cpp +++ b/companion/src/storage/appdata.cpp @@ -343,7 +343,7 @@ bool ComponentData::existsOnDisk() return (m_settings.contains(settingsPath() % "name")); } -void ComponentData::clearRelease() +void ComponentData::releaseClear() { releaseReset(); releaseIdReset(); diff --git a/companion/src/storage/appdata.h b/companion/src/storage/appdata.h index a33bc9173..b0721a3fe 100644 --- a/companion/src/storage/appdata.h +++ b/companion/src/storage/appdata.h @@ -472,7 +472,7 @@ class ComponentData: public CompStoreObj }; Q_ENUM(ReleaseChannel) - void clearRelease(); + void releaseClear(); static QStringList releaseChannelsList() { return { tr("Releases"), tr("Pre-release"), tr("Nightly") } ; } inline ReleaseChannel boundedReleaseChannel() const { diff --git a/companion/src/updates/CMakeLists.txt b/companion/src/updates/CMakeLists.txt index 9acec0d5c..592c9d758 100644 --- a/companion/src/updates/CMakeLists.txt +++ b/companion/src/updates/CMakeLists.txt @@ -4,7 +4,14 @@ set(updates_NAMES updates updatesdialog updateoptionsdialog - repomodels + updatestatus + updatenetwork + repodatamodels + repometadata + reporeleases + repoassets + repo + updateparameters updateinterface updatefirmware updatecompanion @@ -12,6 +19,7 @@ set(updates_NAMES updatesounds updatethemes updatemultiprotocol + updatefactories chooserdialog ) @@ -30,8 +38,15 @@ set(updates_QTS updates.h updatesdialog.h updateoptionsdialog.h - repomodels.h + updatestatus.h + updatenetwork.h + repodatamodels.h + reporeleases.h + repoassets.h + repo.h + updateparameters.h updateinterface.h + updatefactories.h chooserdialog.h ) diff --git a/companion/src/updates/chooserdialog.h b/companion/src/updates/chooserdialog.h index 3eb3dd1f4..e31f5c98e 100644 --- a/companion/src/updates/chooserdialog.h +++ b/companion/src/updates/chooserdialog.h @@ -40,10 +40,6 @@ class ChooserDialog : public QDialog QItemSelectionModel* selectedItems(); - signals: - - private slots: - private: Ui::ChooserDialog *ui; }; diff --git a/companion/src/updates/downloaddialog.h b/companion/src/updates/downloaddialog.h index 71b2c4140..1ea6d822c 100644 --- a/companion/src/updates/downloaddialog.h +++ b/companion/src/updates/downloaddialog.h @@ -51,7 +51,6 @@ class DownloadDialog : public QDialog private: Ui::DownloadDialog *ui; - QNetworkAccessManager qnam; QNetworkReply *reply; QFile *file; diff --git a/companion/src/updates/repo.cpp b/companion/src/updates/repo.cpp new file mode 100644 index 000000000..f656ea743 --- /dev/null +++ b/companion/src/updates/repo.cpp @@ -0,0 +1,59 @@ +/* + * 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 "repo.h" + +Repo::Repo(QObject * parent, UpdateStatus * status, UpdateNetwork * network) : + QObject(parent), + m_status(status), + m_network(network), + m_releases(new RepoReleases(this, status, network)), + m_assets(new RepoAssets(this, status, network)) +{ + connect(m_releases, &RepoReleases::idChanged, [=](const int id) { + m_assets->onReleaseIdChanged(id); + }); +} + +Repo::~Repo() +{ + delete m_assets; + delete m_releases; +} + +bool Repo::getJson(const QString filename, QJsonDocument * json) +{ + m_network->downloadJson(m_releases->urlContent(filename), json); + + if (!m_network->isSuccess()) { + m_status->reportProgress("Unable to download json data", QtDebugMsg); + return false; + } + + return true; +} + +void Repo::init(const QString & repoPath, const QString & nightly, const int resultsPerPage) +{ + m_path = repoPath; + m_releases->init(repoPath, nightly, resultsPerPage); + m_assets->init(repoPath, QString(), resultsPerPage); +} diff --git a/companion/src/updates/repo.h b/companion/src/updates/repo.h new file mode 100644 index 000000000..cb63ac0da --- /dev/null +++ b/companion/src/updates/repo.h @@ -0,0 +1,54 @@ +/* + * 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 "updatestatus.h" +#include "updatenetwork.h" +#include "reporeleases.h" +#include "repoassets.h" + +#include + +constexpr char GH_API_REPOS[] {"https://api.github.com/repos"}; +constexpr char GH_API_REPOS_EDGETX[] {"https://api.github.com/repos/EdgeTX"}; + +class Repo : public QObject +{ + Q_OBJECT + + public: + explicit Repo(QObject * parent, UpdateStatus * status, UpdateNetwork * network); + virtual ~Repo(); + + 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 path() const { return m_path; } + RepoReleases* const releases() const { return m_releases; } + + private: + UpdateStatus* const m_status; + UpdateNetwork* const m_network; + RepoReleases* const m_releases; + RepoAssets* const m_assets; + QString m_path; +}; diff --git a/companion/src/updates/repoassets.cpp b/companion/src/updates/repoassets.cpp new file mode 100644 index 000000000..a78cd1284 --- /dev/null +++ b/companion/src/updates/repoassets.cpp @@ -0,0 +1,209 @@ +/* + * 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 "repoassets.h" + +/* + AssetsRawItemModel +*/ + +AssetsRawItemModel::AssetsRawItemModel() : + RepoRawItemModel("Raw Assets") +{ + +} + +void AssetsRawItemModel::parseJsonObject(const QJsonObject & obj) +{ + QStandardItem * item = new QStandardItem(); + + if (!obj.value("name").isUndefined()) { + item->setText(obj.value("name").toString()); + } + + if (!obj.value("id").isUndefined()) { + item->setData(obj.value("id").toInt(), RIMR_Id); + } + + if (!obj.value("content_type").isUndefined()) { + item->setData(obj.value("content_type").toString(), RIMR_Content); + } + + if (!obj.value("updated_at").isUndefined()) { + item->setData(QDateTime::fromString(obj.value("updated_at").toString(), Qt::ISODate), RIMR_Date); + } + + item->setData(true, RIMR_Available); + item->setData(0, RIMR_Flags); + + appendRow(item); +} + +/* + AssetsFilteredItemModel +*/ + +AssetsFilteredItemModel::AssetsFilteredItemModel(AssetsRawItemModel * assetsRawItemModel) : + RepoFilteredItemModel("Filtered Assets") +{ + setSourceModel(assetsRawItemModel); +} + +const QString AssetsFilteredItemModel::contentType(const int id) const +{ + return value(id, RIMR_Content).toString(); +} + +const QString AssetsFilteredItemModel::copyFilter(const int id) const +{ + return value(id, RIMR_CopyFilter).toString(); +} + +bool AssetsFilteredItemModel::setCopyFilter(const int id, const QString filter) +{ + return setValue(id, RIMR_CopyFilter, QVariant(filter)); +} + +bool AssetsFilteredItemModel::setSubDirectory(const int id, const QString path) +{ + return setValue(id, RIMR_SubDirectory, QVariant(path)); +} + +const QString AssetsFilteredItemModel::subDirectory(const int id) const +{ + return value(id, RIMR_SubDirectory).toString(); +} + +/* + RepoAssets +*/ + +RepoAssets::RepoAssets(QObject * parent, UpdateStatus * status, UpdateNetwork * network) : + QObject(parent), + RepoMetaData(status, network), + m_rawItemModel(new AssetsRawItemModel()), + m_filteredItemModel(new AssetsFilteredItemModel(m_rawItemModel)) +{ + setModels(m_rawItemModel, m_filteredItemModel); +} + +const QString RepoAssets::contentType() const +{ + return m_filteredItemModel->contentType(id()); +} + +const QString RepoAssets::copyFilter() const +{ + return m_filteredItemModel->copyFilter(id()); +} + +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())); + return network()->isSuccess(); +} + +bool RepoAssets::downloadToFile(const int row, QString & downloadDir) +{ + getSetId(row); + QFileInfo f(QString("%1/A%2/%3").arg(downloadDir).arg(id()).arg(name())); + network()->downloadToFile(urlAsset(id()), f.absoluteFilePath()); + return network()->isSuccess(); +} + +void RepoAssets::dumpItemModel(const QString modelName, const QAbstractItemModel * itemModel) const +{ + qDebug() << "Contents of model:" << modelName; + + if (itemModel) { + for (int i = 0; i < itemModel->rowCount(); ++i) { + qDebug().noquote() << "row:" << i; + qDebug().noquote() << " name:" << itemModel->data(itemModel->index(i, 0), Qt::DisplayRole).toString(); + qDebug().noquote() << " id:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Id).toInt(); + qDebug().noquote() << " date:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Date).toDateTime(); + qDebug().noquote() << " content:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Content).toString(); + qDebug().noquote() << " copy filter:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_CopyFilter).toString(); + qDebug().noquote() << " sub dir:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_SubDirectory).toString(); + qDebug().noquote() << " flags:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Flags).toInt(); + qDebug().noquote() << " avail:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Available).toBool(); + } + } +} + +bool RepoAssets::getJson(QJsonDocument * json) +{ + network()->downloadJson(urlAsset(id()), json); + + if (!network()->isSuccess()) { + status()->reportProgress("Unable to download json data", QtDebugMsg); + return false; + } + + return true; +} + +bool RepoAssets::getJson(const QString assetName, QJsonDocument * json) +{ + if (!getSetId(assetName)) + return false; + + return getJson(json); +} + +void RepoAssets::init(const QString & repoPath,const QString & nightly, const int resultsPerPage) +{ + RepoMetaData::init(repoPath, resultsPerPage); +} + +void RepoAssets::onReleaseIdChanged(const int id) +{ + m_releaseId = id; + invalidate(); +} + +bool RepoAssets::retrieveMetaDataAll() +{ + return retrieveMetaData(RepoRawItemModel::MDT_Assets, urlAssets(m_releaseId)); +} + +bool RepoAssets::retrieveMetaDataOne(const int id) +{ + setId(id); + return retrieveMetaData(RepoRawItemModel::MDT_Asset, urlAsset(id)); +} + +bool RepoAssets::setCopyFilter(const QString filter) +{ + return m_filteredItemModel->setCopyFilter(id(), filter); +} + +bool RepoAssets::setSubDirectory(const QString path) +{ + return m_filteredItemModel->setSubDirectory(id(), path); +} + +const QString RepoAssets::subDirectory() const +{ + return m_filteredItemModel->subDirectory(id()); +} diff --git a/companion/src/updates/repoassets.h b/companion/src/updates/repoassets.h new file mode 100644 index 000000000..4ed315873 --- /dev/null +++ b/companion/src/updates/repoassets.h @@ -0,0 +1,93 @@ +/* + * 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 "repodatamodels.h" +#include "repometadata.h" + +class AssetsRawItemModel : public RepoRawItemModel +{ + Q_OBJECT + + public: + explicit AssetsRawItemModel(); + virtual ~AssetsRawItemModel() {} + + protected: + friend class RepoAssets; + + virtual void parseJsonObject(const QJsonObject & obj); +}; + +class AssetsFilteredItemModel : public RepoFilteredItemModel +{ + Q_OBJECT + + public: + explicit AssetsFilteredItemModel(AssetsRawItemModel * assetsRawItemModel); + virtual ~AssetsFilteredItemModel() {}; + + protected: + friend class RepoAssets; + + const QString contentType(const int id) const; + const QString copyFilter(const int id) const; + bool setCopyFilter(const int id, const QString filter); + bool setSubDirectory(const int id, const QString path); + const QString subDirectory(const int id) const; +}; + +class RepoAssets : public QObject, public RepoMetaData +{ + Q_OBJECT + + public slots: + void onReleaseIdChanged(int id); + + public: + 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); + + 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); + bool setCopyFilter(const QString filter); + bool setSubDirectory(const QString path); + const QString subDirectory() const; + + signals: + void idChanged(int id); + + private: + AssetsRawItemModel* const m_rawItemModel; + AssetsFilteredItemModel* const m_filteredItemModel; + int m_releaseId; + + virtual void dumpItemModel(const QString modelName, const QAbstractItemModel * itemModel) const; +}; diff --git a/companion/src/updates/repodatamodels.cpp b/companion/src/updates/repodatamodels.cpp new file mode 100644 index 000000000..a8c11f760 --- /dev/null +++ b/companion/src/updates/repodatamodels.cpp @@ -0,0 +1,245 @@ +/* + * 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 "repodatamodels.h" +#include "constants.h" + +/* + RepoRawItemModel +*/ + +RepoRawItemModel::RepoRawItemModel(QString name) : + QStandardItemModel(), + RepoItemModelBase(name) +{ + invalidate(); +} + +void RepoRawItemModel::invalidate() +{ + clear(); + setRefreshRequired(true); +} + +void RepoRawItemModel::parseAll(QJsonDocument * json) +{ + invalidate(); + + if (json->isArray()) { + const QJsonArray &array = json->array(); + foreach (const QJsonValue &v, array) + { + if (v.isObject()) { + const QJsonObject &obj = v.toObject(); + parseJsonObject(obj); + } + } + setRefreshRequired(false); + } +} + +void RepoRawItemModel::parseMetaData(const int mdt, QJsonDocument * json) +{ + switch (mdt) { + case MDT_Asset: + case MDT_Release: + parseOne(json); + break; + case MDT_Assets: + case MDT_Releases: + parseAll(json); + break; + default: + qDebug() << "Unexpected meta data type" << mdt; + break; + } +} + +void RepoRawItemModel::parseOne(QJsonDocument * json) +{ + invalidate(); + + if (json->isObject()) { + const QJsonObject &obj = json->object(); + parseJsonObject(obj); + setRefreshRequired(false); + } +} + +// static +QString RepoRawItemModel::typeToString(const int val) +{ + const QStringList strl = { "Asset", "Assets", "Release", "Releases" }; + return strl.value(val, CPN_STR_UNKNOWN_ITEM); +} + +void RepoRawItemModel::update() +{ + for (int i = 0; i < rowCount(); ++i) + setDynamicItemData(item(i)); +} + +/* + RepoFilteredItemModel +*/ + +RepoFilteredItemModel::RepoFilteredItemModel(QString name) : + QSortFilterProxyModel(), + RepoItemModelBase(name) +{ + m_filterFlags = 0; + m_filterText.setPattern(""); + m_filterText.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + setFilterRole(RIMR_Available); + setFilterKeyColumn(0); + setDynamicSortFilter(true); +} + +const QString RepoFilteredItemModel::date(const int id) const +{ + return value(id, RIMR_Date).toString(); +} + +bool RepoFilteredItemModel::filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const +{ + const QModelIndex & srcIdx = sourceModel()->index(sourceRow, 0, sourceParent); + + if (!srcIdx.isValid() || !sourceModel()->data(srcIdx, filterRole()).toBool()) + return false; + + if (!m_filterFlags && m_filterText.pattern().isEmpty()) + return true; + + if (m_filterFlags) { + bool ok; + const int flags = sourceModel()->data(srcIdx, RIMR_Flags).toInt(&ok); + return (ok && (m_filterFlags & flags)); + } + else { + return sourceModel()->data(srcIdx, Qt::DisplayRole).toString().contains(m_filterText); + } +} + +const int RepoFilteredItemModel::flags(const int id) const +{ + return value(id, RIMR_Flags).toInt(); +} + +const int RepoFilteredItemModel::id(const int row) const +{ + const QModelIndex & idx = index(row, 0); + + if (!idx.isValid()) + return 0; + + bool ok; + const int id = data(idx, RIMR_Id).toInt(&ok); + + if (ok) + return id; + + return 0; +} + +const int RepoFilteredItemModel::id(const QVariant & value, const Qt::MatchFlags flags, const int role) const +{ + const QModelIndexList idxlst = match(index(0, 0), role, value, 1, flags); + + if (!idxlst.isEmpty()) { + bool ok; + const int id = data(idxlst.at(0), RIMR_Id).toInt(&ok); + + if (ok) + return id; + } + + return 0; +} + +const QStringList RepoFilteredItemModel::list() const +{ + QStringList strl; + + for (int i = 0; i < rowCount(); i++) + strl << data(index(i, 0)).toString(); + + return strl; +} + +const QString RepoFilteredItemModel::name(const int id) const +{ + return value(id, Qt::DisplayRole).toString(); +} + +void RepoFilteredItemModel::setFilterFlags(const int flags) +{ + if (m_filterFlags != flags) { + m_filterFlags = flags; + m_filterText.setPattern(""); + invalidateFilter(); + } +} + +void RepoFilteredItemModel::setFilterPattern(const QString & pattern) +{ + if (m_filterText.pattern() != pattern) { + m_filterText.setPattern(pattern); + m_filterFlags = 0; + invalidateFilter(); + } +} + +bool RepoFilteredItemModel::setFlags(const int id, const int flags) +{ + return setValue(id, RIMR_Flags, QVariant(flags)); +} + +bool RepoFilteredItemModel::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; +} + +const QVariant RepoFilteredItemModel::value(const int id, const int role) const +{ + //qDebug() << "id:" << id << "role:"; + const QModelIndexList idxlst = match(index(0, 0), RIMR_Id, QVariant(id), 1, Qt::MatchExactly); + + if (!idxlst.isEmpty()) + return data(idxlst.at(0), role); + + return QVariant(); +} + +const QMap RepoFilteredItemModel::valuesMap(const int id) const +{ + const QModelIndexList idxlst = match(index(0, 0), RIMR_Id, QVariant(id), 1, Qt::MatchExactly); + + if (!idxlst.isEmpty()) + return itemData(idxlst.at(0)); + + return QMap(); +} diff --git a/companion/src/updates/repodatamodels.h b/companion/src/updates/repodatamodels.h new file mode 100644 index 000000000..bf4b0d466 --- /dev/null +++ b/companion/src/updates/repodatamodels.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include +#include +#include +#include +#include + +class RepoItemModelBase +{ + public: + explicit RepoItemModelBase(QString name) : m_modelName(name) {} + virtual ~RepoItemModelBase() {} + + inline const QString modelName() const { return m_modelName; } + + enum RepoItemModelRole { + RIMR_Id = Qt::UserRole, + RIMR_Date, + RIMR_SortOrder, + RIMR_Tag, + RIMR_Content, + RIMR_Available, + RIMR_Flags, + RIMR_CopyFilter, + RIMR_SubDirectory + }; + + private: + QString m_modelName; +}; + +class RepoRawItemModel : public QStandardItemModel, public RepoItemModelBase +{ + Q_OBJECT + + public: + explicit RepoRawItemModel(QString name); + virtual ~RepoRawItemModel() {} + + enum MetaDataType { + MDT_Asset, + MDT_Assets, + MDT_Release, + MDT_Releases + }; + + static QString typeToString(const int val); + + protected: + friend class RepoMetaData; + friend class RepoAssets; + friend class RepoReleases; + + virtual bool isAvailable(QStandardItem * item) { return true; } + virtual void parseJsonObject(const QJsonObject & obj) = 0; + virtual void setDynamicItemData(QStandardItem * item) {} + + void invalidate(); + const bool isRefreshRequired() const { return m_refreshRequired; } + void parseAll(QJsonDocument * json); + void parseMetaData(const int mdt, QJsonDocument * json); + void parseOne(QJsonDocument * json); + void setRefreshRequired(const bool val) { m_refreshRequired = val; } + void update(); + + private: + bool m_refreshRequired; +}; + +class RepoFilteredItemModel: public QSortFilterProxyModel, public RepoItemModelBase +{ + Q_OBJECT + + public: + explicit RepoFilteredItemModel(QString name); + virtual ~RepoFilteredItemModel() {} + + protected: + friend class RepoMetaData; + friend class RepoAssets; + friend class RepoReleases; + + virtual bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const override; + + const int count() const { return rowCount(); } + const QString date(const int id) const; + const int flags(const int id) const; + const int id(const int row) const ; + const int id(const QVariant & value, const Qt::MatchFlags flags = Qt::MatchExactly, const int role = Qt::DisplayRole) const; + const bool isEmpty() const { return rowCount() == 0; } + const QStringList list() const; + const QString name(const int id) const; + void setFilterFlags(const int flags); + void setFilterPattern(const QString & pattern); + bool setFlags(const int id, const int flags); + bool setValue(const int id, const int role, const QVariant value); + const QVariant value(const int id, const int role) const; + const QMap valuesMap(int id) const; + + private: + int m_filterFlags; + QRegularExpression m_filterText; +}; diff --git a/companion/src/updates/repometadata.cpp b/companion/src/updates/repometadata.cpp new file mode 100644 index 000000000..88a01139b --- /dev/null +++ b/companion/src/updates/repometadata.cpp @@ -0,0 +1,203 @@ +/* + * 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 "repometadata.h" + +RepoMetaData::RepoMetaData(UpdateStatus * status, UpdateNetwork * network) : + m_status(status), + m_network(network), + m_rawItemModel(nullptr), + m_filteredItemModel(nullptr), + m_id(0) +{ + +} + +const int RepoMetaData::count() const +{ + return m_filteredItemModel->count(); +} + +const QString RepoMetaData::date() const +{ + return m_filteredItemModel->date(id()); +} + +void RepoMetaData::dumpRawItemModel() const +{ + dumpItemModel(m_rawItemModel->modelName(), m_rawItemModel); +} + +void RepoMetaData::dumpFilteredItemModel() const +{ + dumpItemModel(m_filteredItemModel->modelName(), m_filteredItemModel); +} + +const int RepoMetaData::flags() const +{ + return m_filteredItemModel->flags(id()); +} + +int RepoMetaData::getSetId() +{ + return getSetId(0); +} + +int RepoMetaData::getSetId(const int row) +{ + return setId(m_filteredItemModel->id(row)); +} + +int RepoMetaData::getSetId(QVariant value, Qt::MatchFlags flags, int role) +{ + return setId(m_filteredItemModel->id(value, flags, role)); +} + +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); + m_rawItemModel->invalidate(); +} + +const int RepoMetaData::isEmpty() const +{ + return m_filteredItemModel->isEmpty(); +} + +const bool RepoMetaData::isIdValid() const +{ + return m_id > 0; +} + +const QStringList RepoMetaData::list() const +{ + return m_filteredItemModel->list(); +} + +const QString RepoMetaData::name() const +{ + return m_filteredItemModel->name(id()); +} + +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()) + return true; + + QJsonDocument *json = new QJsonDocument(); + + network()->downloadMetaData(url, json); + + if (!network()->isSuccess()) { + status()->reportProgress("Unable to download release asset meta data", QtDebugMsg); + invalidate(); + return false; + } + + m_rawItemModel->parseMetaData(mdt, json); + + // set current id to first row as most methods assume a current id has been set + getSetId(); + + delete json; + + return true; +} + +bool RepoMetaData::setFlags(int flags) +{ + return m_filteredItemModel->setFlags(id(), flags); +} + +void RepoMetaData::setFilterFlags(int flags) +{ + m_filteredItemModel->setFilterFlags(flags); +} + +void RepoMetaData::setFilterPattern(const QString & pattern) +{ + m_filteredItemModel->setFilterPattern(pattern); +} + +int RepoMetaData::setId(const int id) +{ + m_id = id; + return m_id; +} + +void RepoMetaData::setModels(RepoRawItemModel * repoRawItemModel, RepoFilteredItemModel * repoFilteredItemModel) +{ + m_rawItemModel = repoRawItemModel; + m_filteredItemModel = repoFilteredItemModel; + invalidate(); +} + +UpdateStatus* const RepoMetaData::status() const +{ + return m_status; +} + +const QString RepoMetaData::urlAsset(const int assetId) const +{ + return QString("%1/assets/%2").arg(urlReleases()).arg(assetId); +} + +const QString RepoMetaData::urlAssets(const int releaseId) const +{ + return QString("%1/%2/assets").arg(urlReleases()).arg(releaseId) % (m_resultsPerPage > -1 ? + QString("\?per_page=%1").arg(m_resultsPerPage) : ""); +} + +const QString RepoMetaData::urlContent(const QString & filename) const +{ + return QString("%1/contents/%2").arg(m_repoPath).arg(filename); +} + +const QString RepoMetaData::urlReleases() const +{ + return QString("%1/releases").arg(m_repoPath); +} diff --git a/companion/src/updates/repometadata.h b/companion/src/updates/repometadata.h new file mode 100644 index 000000000..f897b450c --- /dev/null +++ b/companion/src/updates/repometadata.h @@ -0,0 +1,83 @@ +/* + * 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 "updatestatus.h" +#include "updatenetwork.h" +#include "repodatamodels.h" + +#include + +class QAbstractItemModel; + +class RepoMetaData +{ + public: + 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); + + const int count() const; + const QString date() const; + void dumpRawItemModel() const; + void dumpFilteredItemModel() const; + const int flags() const; + int getSetId(); + 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; + UpdateNetwork* const m_network; + RepoRawItemModel* m_rawItemModel; + RepoFilteredItemModel* m_filteredItemModel; + + QString m_repoPath; + int m_resultsPerPage; + int m_id; + + virtual void dumpItemModel(const QString modelName, const QAbstractItemModel * itemModel) const = 0; +}; diff --git a/companion/src/updates/repomodels.cpp b/companion/src/updates/repomodels.cpp deleted file mode 100644 index 8890384dc..000000000 --- a/companion/src/updates/repomodels.cpp +++ /dev/null @@ -1,546 +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 "repomodels.h" -#include "appdata.h" -#include "helpers.h" - -#include -#include - -QString metaDataTypeToString(MetaDataType val) -{ - switch ((int)val) { - case MDT_Release: - return "Release"; - case MDT_Releases: - return "Releases"; - case MDT_Asset: - return "Asset"; - case MDT_ReleaseAssets: - return "Release Assets"; - default: - return CPN_STR_UNKNOWN_ITEM; - } -} - -/* - ReleasesItemModel -*/ - -ReleasesItemModel::ReleasesItemModel() : - UpdatesItemModel("Releases"), - m_settingsIdx(-1), - m_nightlyName(""), - m_releaseChannel(-1), - m_refreshRequired(true) -{ - setSortRole(UpdatesItemModel::IMDR_SortOrder); -} - -void ReleasesItemModel::setReleaseChannel(const int channel) -{ - if (m_releaseChannel != channel) { - m_releaseChannel = channel; - update(); - } -} - -void ReleasesItemModel::parseMetaData(const int mdt, QJsonDocument * jsonDoc) -{ - json = jsonDoc; - - switch (mdt) { - case MDT_Release: - parseRelease(); - break; - case MDT_Releases: - parseReleases(); - break; - } -} - -void ReleasesItemModel::parseRelease() -{ - clear(); - - if (json->isObject()) { - const QJsonObject &obj = json->object(); - parseJsonObject(obj); - } -} - -void ReleasesItemModel::parseReleases() -{ - clear(); - - if (json->isArray()) { - const QJsonArray &array = json->array(); - foreach (const QJsonValue &v, array) - { - if (v.isObject()) { - const QJsonObject &obj = v.toObject(); - parseJsonObject(obj); - } - } - } - - sort(0, Qt::DescendingOrder); - m_refreshRequired = false; -} - -void ReleasesItemModel::parseJsonObject(const QJsonObject & obj) -{ - QStandardItem * item = new QStandardItem(); - - QString tag = QString(); - bool prerelease = false; - - if (!obj.value("name").isUndefined()) { - item->setText(obj.value("name").toString()); - } - - if (!obj.value("id").isUndefined()) { - item->setData(obj.value("id").toInt(), UpdatesItemModel::IMDR_Id); - } - - if (!obj.value("tag_name").isUndefined()) { - tag = obj.value("tag_name").toString(); - item->setData(tag, UpdatesItemModel::IMDR_Tag); - } - - if (!obj.value("prerelease").isUndefined()) { - prerelease = obj.value("prerelease").toBool(); - item->setData(prerelease, UpdatesItemModel::IMDR_Prerelease); - } - - if (!obj.value("created_at").isUndefined()) { - item->setData(QDateTime::fromString(obj.value("created_at").toString(), Qt::ISODate), UpdatesItemModel::IMDR_Date); - } - - setDynamicItemData(item); - - SemanticVersion sv; - - if (tag.toLower() == m_nightlyName) - sv.fromString("255.255.255"); - else - sv.fromString(tag); - - if (sv.isValid()) - item->setData(sv.toInt(), UpdatesItemModel::IMDR_SortOrder); - else - item->setData(item->data(IMDR_Date).toInt(), UpdatesItemModel::IMDR_SortOrder); - - appendRow(item); -} - -void ReleasesItemModel::setDynamicItemData(QStandardItem * item) -{ - item->setData(isReleaseAvailable(item), UpdatesItemModel::IMDR_Available); -} - -void ReleasesItemModel::update() -{ - for (int i = 0; i < rowCount(); ++i) - setDynamicItemData(item(i)); -} - -bool ReleasesItemModel::isReleaseAvailable(QStandardItem * item) -{ - if (item->data(IMDR_Prerelease).toBool()) { - if (m_releaseChannel == ComponentData::RELEASE_CHANNEL_STABLE) { - //qDebug() << "Release channel stable" << tagname << "is pre-release" ; - return false; - } - else if (m_releaseChannel == ComponentData::RELEASE_CHANNEL_PRERELEASE && - item->data(IMDR_Tag).toString().toLower() == m_nightlyName) { - //qDebug() << "Release channel pre-release" << tagname << "is nightly" ; - return false; - } - } - //qDebug() << "Release available:" << tagname; - return true; -} - -/* - AssetsItemModel -*/ - -AssetsItemModel::AssetsItemModel() : - UpdatesItemModel("Assets") -{ - -} - -void AssetsItemModel::parseMetaData(int mdt, QJsonDocument * jsonDoc) -{ - json = jsonDoc; - - switch (mdt) { - case MDT_Asset: - parseAsset(); - break; - case MDT_ReleaseAssets: - parseReleaseAssets(); - break; - } -} - -void AssetsItemModel::parseAsset() -{ - clear(); - - if (json->isObject()) { - const QJsonObject &obj = json->object(); - parseJsonObject(obj); - } -} - -void AssetsItemModel::parseReleaseAssets() -{ - clear(); - - if (json->isArray()) { - const QJsonArray &array = json->array(); - foreach (const QJsonValue &v, array) - { - if (v.isObject()) { - const QJsonObject &obj = v.toObject(); - parseJsonObject(obj); - } - } - } - - sort(0); -} - -void AssetsItemModel::parseJsonObject(const QJsonObject & obj) -{ - QStandardItem * item = new QStandardItem(); - - if (!obj.value("name").isUndefined()) { - item->setText(obj.value("name").toString()); - } - - if (!obj.value("id").isUndefined()) { - item->setData(obj.value("id").toInt(), UpdatesItemModel::IMDR_Id); - } - - if (!obj.value("content_type").isUndefined()) { - item->setData(obj.value("content_type").toString(), UpdatesItemModel::IMDR_Content); - } - - if (!obj.value("updated_at").isUndefined()) { - item->setData(QDateTime::fromString(obj.value("updated_at").toString(), Qt::ISODate), UpdatesItemModel::IMDR_Date); - } - - item->setData(true, UpdatesItemModel::IMDR_Available); - - appendRow(item); -} - -/* - UpdatesFilteredItemModel -*/ - -UpdatesFilteredItemModel::UpdatesFilteredItemModel(UpdatesItemModel * sourceModel, QString name) : - QSortFilterProxyModel(nullptr), - filterFlags(0), - m_name(name) -{ - filterText.setPattern(""); - filterText.setPatternOptions(QRegularExpression::CaseInsensitiveOption); - setFilterRole(UpdatesItemModel::IMDR_Available); - setFilterKeyColumn(0); - setDynamicSortFilter(true); - setSourceModel(sourceModel); -} - -void UpdatesFilteredItemModel::setFilterFlags(const int flags) -{ - if (filterFlags != flags) { - filterFlags = flags; - filterText.setPattern(""); - invalidateFilter(); - } -} - -void UpdatesFilteredItemModel::setFilterPattern(const QString & pattern) -{ - if (filterText.pattern() != pattern) { - filterText.setPattern(pattern); - filterFlags = 0; - invalidateFilter(); - } -} - -bool UpdatesFilteredItemModel::filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const -{ - const QModelIndex & srcIdx = sourceModel()->index(sourceRow, 0, sourceParent); - - if (!srcIdx.isValid() || !sourceModel()->data(srcIdx, filterRole()).toBool()) - return false; - - if (!filterFlags && filterText.pattern().isEmpty()) - return true; - - if (filterFlags) { - bool ok; - const int flags = sourceModel()->data(srcIdx, UpdatesItemModel::IMDR_Flags).toInt(&ok); - - return (ok && (!flags || (filterFlags & flags))); - } - else { - return sourceModel()->data(srcIdx, Qt::DisplayRole).toString().contains(filterText); - } -} - -void UpdatesFilteredItemModel::dumpContents() -{ - qDebug() << "Contents of" << m_name; - - for (int i = 0; i < rowCount(); ++i) { - qDebug() << "row:" << i - << "text:" << data(index(i, 0), Qt::DisplayRole).toString() - << "id:" << data(index(i, 0), UpdatesItemModel::IMDR_Id).toInt() - << "date:" << data(index(i, 0), UpdatesItemModel::IMDR_Date).toDateTime() - << "tag:" << data(index(i, 0), UpdatesItemModel::IMDR_Tag).toString() - << "avail:" << data(index(i, 0), UpdatesItemModel::IMDR_Available).toBool() - << "flags:" << data(index(i, 0), UpdatesItemModel::IMDR_Flags).toInt() - << "content:" << data(index(i, 0), UpdatesItemModel::IMDR_Content).toString() - << "prerelease:" << data(index(i, 0), UpdatesItemModel::IMDR_Prerelease).toBool(); - } -} - -QMap UpdatesFilteredItemModel::itemMetaData(const int id) -{ - const QModelIndexList idxlst = match(index(0, 0), UpdatesItemModel::IMDR_Id, QVariant(id), 1, Qt::MatchExactly); - - if (!idxlst.isEmpty()) - return itemData(idxlst.at(0)); - - return QMap(); -} - -QVariant UpdatesFilteredItemModel::metaDataValue(const int id, const int role) -{ - //qDebug() << "id:" << id << "role:"; - - const QModelIndexList idxlst = match(index(0, 0), UpdatesItemModel::IMDR_Id, QVariant(id), 1, Qt::MatchExactly); - - if (!idxlst.isEmpty()) - return data(idxlst.at(0), role); - - return QVariant(); -} - -bool UpdatesFilteredItemModel::setMetaDataValue(const int id, const int role, const QVariant value) -{ - //qDebug() << "id:" << id << "role:" << role << "value:" << value; - - const QModelIndexList idxlst = match(index(0, 0), UpdatesItemModel::IMDR_Id, QVariant(id), 1, Qt::MatchExactly); - - if (!idxlst.isEmpty()) - return setData(idxlst.at(0), value, role); - - return false; -} - -int UpdatesFilteredItemModel::id(const int row) -{ - const QModelIndex & idx = index(row, 0); - - if (!idx.isValid()) - return 0; - - bool ok; - const int id = data(idx, UpdatesItemModel::IMDR_Id).toInt(&ok); - - if (ok) - return id; - - return 0; -} - -int UpdatesFilteredItemModel::id(const QVariant value, const Qt::MatchFlags flags, const int role) -{ - const QModelIndexList idxlst = match(index(0, 0), role, value, 1, flags); - - if (!idxlst.isEmpty()) { - bool ok; - const int id = data(idxlst.at(0), UpdatesItemModel::IMDR_Id).toInt(&ok); - - if (ok) - return id; - } - - return 0; -} - -QStringList UpdatesFilteredItemModel::list() -{ - QStringList strl; - - for (int i = 0; i < rowCount(); i++) - strl << data(index(i, 0)).toString(); - - return strl; -} - -/* - ReleasesFilteredItemModel -*/ - -ReleasesFilteredItemModel::ReleasesFilteredItemModel(UpdatesItemModel * sourceModel) : - UpdatesFilteredItemModel(sourceModel, "Filtered Releases") -{ - setSortRole(UpdatesItemModel::IMDR_Date); -} - -int ReleasesFilteredItemModel::channelLatestId() -{ - return rows() > 0 ? id(0) : 0; // assumes model sorted descending date -} - -/* - AssetsFilteredItemModel -*/ - -AssetsFilteredItemModel::AssetsFilteredItemModel(UpdatesItemModel * sourceModel) : - UpdatesFilteredItemModel(sourceModel, "Filtered Assets") -{ - -} - -bool AssetsFilteredItemModel::setFlags(const int id, const int flags) -{ - return setMetaDataValue(id, UpdatesItemModel::IMDR_Flags, QVariant(flags)); -} - -bool AssetsFilteredItemModel::setSubDirectory(const int id, const QString path) -{ - return setMetaDataValue(id, UpdatesItemModel::IMDR_SubDirectory, QVariant(path)); -} - -bool AssetsFilteredItemModel::setCopyFilter(const int id, const QString filter) -{ - return setMetaDataValue(id, UpdatesItemModel::IMDR_CopyFilter, QVariant(filter)); -} - - -/* - RepoMetaData -*/ - -RepoMetaData::RepoMetaData(QObject * parent) : - QObject(parent) -{ - -} - -/* - ReleasesMetaData -*/ - -ReleasesMetaData::ReleasesMetaData(QObject * parent) : - RepoMetaData(parent), - m_id(0) -{ - itemModel = new ReleasesItemModel(); - filteredItemModel = new ReleasesFilteredItemModel(itemModel); -} - -void ReleasesMetaData::init(const QString repo, const QString nightly, const int settingsIndex, const int resultsPerPage) -{ - m_repo = repo; - m_resultsPerPage = resultsPerPage; - itemModel->setNightlyName(nightly); - itemModel->setSettingsIndex(settingsIndex); -} - -bool ReleasesMetaData::refreshRequired() -{ - return m_id == 0 ? true : itemModel->refreshRequired(); -} - -int ReleasesMetaData::getSetId() -{ - m_id = filteredItemModel->channelLatestId(); - return m_id; -} - -int ReleasesMetaData::getSetId(int row) -{ - m_id = filteredItemModel->id(row); - return m_id; -} - -int ReleasesMetaData::getSetId(QVariant value, Qt::MatchFlags flags, int role) -{ - m_id = filteredItemModel->id(value, flags, role); - return m_id; -} - -QString ReleasesMetaData::name() -{ - if (m_id) - return filteredItemModel->name(m_id); - - return ""; -} - -QString ReleasesMetaData::version() -{ - if (m_id) - return filteredItemModel->version(m_id); - - return "0"; -} - -/* - AssetsMetaData -*/ - -AssetsMetaData::AssetsMetaData(QObject * parent) : - RepoMetaData(parent) -{ - itemModel = new AssetsItemModel(); - filteredItemModel = new AssetsFilteredItemModel(itemModel); -} - -void AssetsMetaData::init(const QString repo, const int resultsPerPage) -{ - m_repo = repo; - m_resultsPerPage = resultsPerPage; -} - -int AssetsMetaData::getSetId(int row) -{ - m_id = filteredItemModel->id(row); - return m_id; -} - -int AssetsMetaData::getSetId(QVariant value, Qt::MatchFlags flags, int role) -{ - m_id = filteredItemModel->id(value, flags, role); - return m_id; -} diff --git a/companion/src/updates/repomodels.h b/companion/src/updates/repomodels.h deleted file mode 100644 index 468704b0e..000000000 --- a/companion/src/updates/repomodels.h +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) EdgeTX - * - * Based on code named - * opentx - https://github.com/opentx/opentx - * th9x - http://code.google.com/p/th9x - * er9x - http://code.google.com/p/er9x - * gruvin9x - http://code.google.com/p/gruvin9x - * - * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#pragma once - -#include -#include -#include - -constexpr char GITHUB_API_REPOS[] {"https://api.github.com/repos"}; -constexpr char GH_REPOS_EDGETX[] {"https://api.github.com/repos/EdgeTX"}; - -constexpr char GH_ACCEPT_HEADER_BINARY[] {"application/octet-stream"}; -constexpr char GH_ACCEPT_HEADER_METADATA[] {"application/vnd.github.v3+json"}; -constexpr char GH_ACCEPT_HEADER_RAW[] {"application/vnd.github.v3.raw"}; - -enum MetaDataType { - MDT_Release, - MDT_Releases, - MDT_Asset, - MDT_ReleaseAssets -}; - -QString metaDataTypeToString(MetaDataType val); - -class UpdatesItemModel : public QStandardItemModel -{ - Q_OBJECT - - public: - enum ItemModelDataRoles { - IMDR_Id = Qt::UserRole, - IMDR_Date, - IMDR_SortOrder, - IMDR_Tag, - IMDR_Prerelease, - IMDR_Content, - IMDR_Available, - IMDR_Flags, - IMDR_CopyFilter, - IMDR_SubDirectory - }; - Q_ENUM(ItemModelDataRoles) - - explicit UpdatesItemModel(QString name = "") : - QStandardItemModel(nullptr), - json(nullptr), - m_name(name) {} - - virtual ~UpdatesItemModel() {} - - protected: - QJsonDocument * json; - - private: - QString m_name; -}; - -class ReleasesItemModel : public UpdatesItemModel -{ - Q_OBJECT - - friend class ReleasesMetaData; - - public: - explicit ReleasesItemModel(); - virtual ~ReleasesItemModel() {} - - void setNightlyName(const QString name) { m_nightlyName = name.toLower(); } - void setSettingsIndex(const int index) { m_settingsIdx = index; } - void setReleaseChannel(const int channel); - - void parseMetaData(const int mdt, QJsonDocument * json); - int settingsIndex() { return m_settingsIdx; } - bool refreshRequired() { return m_refreshRequired; } - - private: - int m_settingsIdx; - QString m_nightlyName; - int m_releaseChannel; - bool m_refreshRequired; - - void setDynamicItemData(QStandardItem * item); - void update(); - bool isReleaseAvailable(QStandardItem * item); - - void parseRelease(); - void parseReleases(); - void parseJsonObject(const QJsonObject & obj); -}; - -class AssetsItemModel : public UpdatesItemModel -{ - Q_OBJECT - - friend class AssetsMetaData; - - public: - explicit AssetsItemModel(); - virtual ~AssetsItemModel() {} - - void parseMetaData(int mdt, QJsonDocument * json); - - private: - void parseAsset(); - void parseReleaseAssets(); - void parseJsonObject(const QJsonObject & obj); -}; - -class UpdatesFilteredItemModel: public QSortFilterProxyModel -{ - Q_OBJECT - public: - explicit UpdatesFilteredItemModel(UpdatesItemModel * sourceModel, QString name = ""); - virtual ~UpdatesFilteredItemModel() {}; - - void dumpContents(); - QStringList list(); - - int id(const int row); - int id(const QVariant value, const Qt::MatchFlags flags = Qt::MatchExactly, const int role = Qt::DisplayRole); - int rows() { return rowCount(); } - - QString date(const int id) { return metaDataValue(id, UpdatesItemModel::IMDR_Date).toString(); } - QString name(const int id) { return metaDataValue(id, Qt::DisplayRole).toString(); } - - public slots: - void setFilterFlags(const int flags); - void setFilterPattern(const QString & pattern); - - signals: - - protected: - int filterFlags; - QRegularExpression filterText; - - bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const override; - - QMap itemMetaData(int id); - QVariant metaDataValue(const int id, const int role); - bool setMetaDataValue(const int id, const int role, const QVariant value); - - private: - QString m_name; -}; - -class ReleasesFilteredItemModel: public UpdatesFilteredItemModel -{ - Q_OBJECT - - public: - - protected: - friend class ReleasesMetaData; - - explicit ReleasesFilteredItemModel(UpdatesItemModel * sourceModel); - virtual ~ReleasesFilteredItemModel() {}; - - int channelLatestId(); - - bool prerelease(const int id) { return metaDataValue(id, UpdatesItemModel::IMDR_Prerelease).toBool(); } - QString version(const int id) { return metaDataValue(id, UpdatesItemModel::IMDR_Tag).toString(); } - - private: -}; - -class AssetsFilteredItemModel : public UpdatesFilteredItemModel -{ - Q_OBJECT - - public: - - protected: - friend class AssetsMetaData; - - explicit AssetsFilteredItemModel(UpdatesItemModel * sourceModel); - virtual ~AssetsFilteredItemModel() {}; - - bool setFlags(const int id, const int flags); - bool setSubDirectory(const int id, const QString path); - bool setCopyFilter(const int id, const QString filter); - - QString filename(int id) { return metaDataValue(id, Qt::DisplayRole).toString(); } - - private: -}; - -class RepoMetaData : public QObject -{ - Q_OBJECT - public: - explicit RepoMetaData(QObject * parent); - virtual ~RepoMetaData() {}; - - const QString urlReleases() { return QString("%1/releases").arg(m_repo); } - const QString urlReleaseLatest() { return QString("%1/latest").arg(urlReleases()); } - const QString urlContent(const QString & path) { return QString("%1/contents/%2").arg(m_repo).arg(path); } - - protected: - QString m_repo; - int m_resultsPerPage; -}; - -class ReleasesMetaData : public RepoMetaData -{ - Q_OBJECT - public: - explicit ReleasesMetaData(QObject * parent); - virtual ~ReleasesMetaData() {}; - - void init(const QString repo, const QString nightly, const int settingsIndex, const int resultsPerPage); - - bool refreshRequired(); - void setReleaseChannel(const int channel) { itemModel->setReleaseChannel(channel); } - - void setId(int id) { m_id = id; } - int id() { return m_id; } - - int getSetId(); - int getSetId(int row); - int getSetId(QVariant value, Qt::MatchFlags flags = Qt::MatchExactly, int role = Qt::DisplayRole); - - void parseMetaData(int mdt, QJsonDocument * jsonDoc) { itemModel->parseMetaData(mdt, jsonDoc); } - - int count() { return filteredItemModel->rows(); } - QStringList list() { return filteredItemModel->list(); } - void dumpContentsFiltered() { filteredItemModel->dumpContents(); } - - QString date() { return filteredItemModel->date(m_id); } - QString name(); - bool prerelease() { return filteredItemModel->prerelease(m_id); } - QString version(); - - const QString urlRelease() { return QString("%1/%2").arg(urlReleases()).arg(m_id); } - - private: - ReleasesItemModel *itemModel; - ReleasesFilteredItemModel *filteredItemModel; - int m_id; -}; - -class AssetsMetaData : public RepoMetaData -{ - Q_OBJECT - public: - explicit AssetsMetaData(QObject * parent); - virtual ~AssetsMetaData() {}; - - void init(const QString repo, const int resultsPerPage); - - void setId(int id) { m_id = id; } - int id() { return m_id; } - - int getSetId(int row); - int getSetId(QVariant value, Qt::MatchFlags flags = Qt::MatchExactly, int role = Qt::DisplayRole); - - void parseMetaData(int mdt, QJsonDocument * jsonDoc) { itemModel->parseMetaData(mdt, jsonDoc); } - - bool setFlags(int flags) { return filteredItemModel->setFlags(m_id, flags); } - - void setFilterFlags(int flags) { filteredItemModel->setFilterFlags(flags); } - void setFilterPattern(const QString & pattern) { filteredItemModel->setFilterPattern(pattern); } - - bool setSubDirectory(const QString path) { return filteredItemModel->setSubDirectory(m_id, path); } - bool setCopyFilter(const QString filter) { return filteredItemModel->setCopyFilter(m_id, filter); } - - int count() { return filteredItemModel->rows(); } - QStringList list() { return filteredItemModel->list(); } - void dumpContentsFiltered() { filteredItemModel->dumpContents(); } - - QString date() { return filteredItemModel->date(m_id); } - QString name() { return filteredItemModel->name(m_id); } - QString filename() { return filteredItemModel->metaDataValue(m_id, Qt::DisplayRole).toString(); } - QString contentType() { return filteredItemModel->metaDataValue(m_id, UpdatesItemModel::IMDR_Content).toString(); } - QString subDirectory() { return filteredItemModel->metaDataValue(m_id, UpdatesItemModel::IMDR_SubDirectory).toString(); } - QString copyFilter() { return filteredItemModel->metaDataValue(m_id, UpdatesItemModel::IMDR_CopyFilter).toString(); } - int flags() { return filteredItemModel->metaDataValue(m_id, UpdatesItemModel::IMDR_Flags).toInt(); } - - const QString urlReleaseAssets(int releaseId) { - return QString("%1/%2/assets").arg(urlReleases()).arg(releaseId) % (m_resultsPerPage > -1 ? - QString("\?per_page=%1").arg(m_resultsPerPage) : ""); } - const QString urlAsset() { return QString("%1/assets/%2").arg(urlReleases()).arg(m_id); } - - private: - AssetsItemModel *itemModel; - AssetsFilteredItemModel *filteredItemModel; - int m_id; -}; diff --git a/companion/src/updates/reporeleases.cpp b/companion/src/updates/reporeleases.cpp new file mode 100644 index 000000000..f91ef6914 --- /dev/null +++ b/companion/src/updates/reporeleases.cpp @@ -0,0 +1,219 @@ +/* + * 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 "reporeleases.h" +#include "appdata.h" +#include "helpers.h" + +/* + ReleasesItemModels +*/ + +// static +const QString ReleasesItemModels::flagsToString(const int flags) +{ + QString str; + + for (int i = 0; i < 16; i++) { + int flag = flags & (1 << i); + if (flag) { + if (!str.isEmpty()) + str.append(";"); + str.append(flagToString(flag)); + } + } + + return str; +} + +// static +const QString ReleasesItemModels::flagToString(const int val) +{ + const QStringList strl = { "None", "Stable", "Pre-release", "Nightly" }; + return strl.value(val, CPN_STR_UNKNOWN_ITEM); +}; + +/* + ReleasesRawItemModel +*/ + +ReleasesRawItemModel::ReleasesRawItemModel() : + RepoRawItemModel("Raw Releases"), + m_nightlyName("") +{ + setSortRole(RIMR_SortOrder); +} + +void ReleasesRawItemModel::parseJsonObject(const QJsonObject & obj) +{ + QStandardItem * item = new QStandardItem(); + + int flags = RELFLG_Stable; + bool available = true; + + if (!obj.value("name").isUndefined()) { + item->setText(obj.value("name").toString()); + } + + if (!obj.value("id").isUndefined()) { + item->setData(obj.value("id").toInt(), RIMR_Id); + } + + if (!obj.value("created_at").isUndefined()) { + item->setData(QDateTime::fromString(obj.value("created_at").toString(), Qt::ISODate), RIMR_Date); + } + + if (!obj.value("prerelease").isUndefined()) { + if (obj.value("prerelease").toBool()) + flags = RELFLG_PreRelease; + } + + if (!obj.value("tag_name").isUndefined()) { + item->setData(obj.value("tag_name").toString(), RIMR_Tag); + if (item->data(RIMR_Tag).toString().toLower() == m_nightlyName) + flags = RELFLG_Nightly; + } + + if (!obj.value("draft").isUndefined()) { + if (obj.value("draft").toBool()) + available = false; + } + + item->setData(available, RIMR_Available); + item->setData(flags, RIMR_Flags); + + SemanticVersion sv; + + if (item->data(RIMR_Tag).toString().toLower() == m_nightlyName) + sv.fromString("255.255.255"); + else + sv.fromString(item->data(RIMR_Tag).toString()); + + if (sv.isValid()) + item->setData(sv.toInt(), RIMR_SortOrder); + else + item->setData(item->data(RIMR_Date).toInt(), RIMR_SortOrder); + + appendRow(item); +} + +void ReleasesRawItemModel::setNightlyName(const QString name) +{ + m_nightlyName = name.toLower(); +} + +/* + ReleasesFilteredItemModel +*/ + +ReleasesFilteredItemModel::ReleasesFilteredItemModel(ReleasesRawItemModel * releasesRawItemModel) : + RepoFilteredItemModel("Filtered Releases") +{ + setSourceModel(releasesRawItemModel); + setSortRole(RIMR_Date); +} + +const int ReleasesFilteredItemModel::channelLatestId() const +{ + return rowCount() > 0 ? id(0) : 0; +} + +void ReleasesFilteredItemModel::setChannel(const int channel) +{ + int flags = 0; + + for (int i = 0; i <= channel; i++) + flags |= (1 << (i + 1)); + + setFilterFlags(flags); +} + +const QString ReleasesFilteredItemModel::version(const int id) const +{ + return value(id, RIMR_Tag).toString(); +} + +/* + RepoReleases +*/ + +RepoReleases::RepoReleases(QObject * parent, UpdateStatus * status, UpdateNetwork * network) : + QObject(parent), + RepoMetaData(status, network), + m_rawItemModel(new ReleasesRawItemModel()), + m_filteredItemModel(new ReleasesFilteredItemModel(m_rawItemModel)) +{ + setModels(m_rawItemModel, m_filteredItemModel); +} + +int RepoReleases::channelLatestId() +{ + return m_filteredItemModel->channelLatestId(); +} + +void RepoReleases::dumpItemModel(const QString modelName, const QAbstractItemModel * itemModel) const +{ + qDebug() << "Contents of model:" << modelName; + + if (itemModel) { + for (int i = 0; i < itemModel->rowCount(); ++i) { + qDebug().noquote() << "row:" << i; + qDebug().noquote() << " name:" << itemModel->data(itemModel->index(i, 0), Qt::DisplayRole).toString(); + qDebug().noquote() << " id:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Id).toInt(); + qDebug().noquote() << " date:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Date).toDateTime(); + qDebug().noquote() << " version:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Tag).toString(); + qDebug().noquote() << " flags:" << ReleasesItemModels::flagsToString(itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Flags).toInt()); + qDebug().noquote() << " avail:" << itemModel->data(itemModel->index(i, 0), RepoItemModelBase::RIMR_Available).toBool(); + } + } +} + +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()); +} + +int RepoReleases::setId(const int id) +{ + if (this->id() != id) { + RepoMetaData::setId(id); + emit idChanged(this->id()); + } + + return this->id(); +} + +void RepoReleases::setChannel(const int channel) +{ + m_filteredItemModel->setChannel(channel); + getSetId(); +} + +const QString RepoReleases::version() const +{ + return m_filteredItemModel->version(id()); +} diff --git a/companion/src/updates/reporeleases.h b/companion/src/updates/reporeleases.h new file mode 100644 index 000000000..5635bd5a3 --- /dev/null +++ b/companion/src/updates/reporeleases.h @@ -0,0 +1,104 @@ +/* + * 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 "repodatamodels.h" +#include "repometadata.h" + +class ReleasesItemModels +{ + public: + explicit ReleasesItemModels() {} + virtual ~ReleasesItemModels() {} + + enum ReleaseFlag { + RELFLG_None = 0, + RELFLG_Stable = 1 << 1, + RELFLG_PreRelease = 1 << 2, + RELFLG_Nightly = 1 << 3, + }; + + static const QString flagsToString(const int flags); + static const QString flagToString(const int flag); +}; + +class ReleasesRawItemModel : public RepoRawItemModel, public ReleasesItemModels +{ + Q_OBJECT + + public: + explicit ReleasesRawItemModel(); + virtual ~ReleasesRawItemModel() {} + + protected: + friend class RepoReleases; + + virtual void parseJsonObject(const QJsonObject & obj); + + void setNightlyName(const QString name); + + private: + QString m_nightlyName; +}; + +class ReleasesFilteredItemModel: public RepoFilteredItemModel, public ReleasesItemModels +{ + Q_OBJECT + + public: + explicit ReleasesFilteredItemModel(ReleasesRawItemModel * releasesRawItemModel); + virtual ~ReleasesFilteredItemModel() {} + + protected: + friend class RepoReleases; + + const int channelLatestId() const; + void setChannel(const int channel); + const QString version(const int id) const; +}; + +class RepoReleases : public QObject, public RepoMetaData +{ + Q_OBJECT + + public: + explicit RepoReleases(QObject * parent, UpdateStatus * status, UpdateNetwork * network); + virtual ~RepoReleases() {} + + virtual void init(const QString & repoPath, const QString & nightly, const int resultsPerPage); + virtual int setId(const int id) override; + virtual bool retrieveMetaDataAll(); + virtual bool retrieveMetaDataOne(const int id) { return false; } // not implemented + + int channelLatestId(); + void setChannel(const int channel); + const QString version() const; + + signals: + void idChanged(int id); + + private: + ReleasesRawItemModel* const m_rawItemModel; + ReleasesFilteredItemModel* const m_filteredItemModel; + + virtual void dumpItemModel(const QString modelName, const QAbstractItemModel * itemModel) const; +}; diff --git a/companion/src/updates/updatecompanion.cpp b/companion/src/updates/updatecompanion.cpp index 79bc270f7..ba85770d7 100644 --- a/companion/src/updates/updatecompanion.cpp +++ b/companion/src/updates/updatecompanion.cpp @@ -53,14 +53,14 @@ #endif UpdateCompanion::UpdateCompanion(QWidget * parent) : - UpdateInterface(parent) + UpdateInterface(parent, CID_Companion, QCoreApplication::translate("UpdateCompanion", "Companion")) { - init(CID_Companion, QCoreApplication::translate("UpdateCompanion", "Companion"), QString(GH_REPOS_EDGETX).append("/edgetx"), "nightly"); + init(QString(GH_API_REPOS_EDGETX).append("/edgetx"), "nightly"); } -void UpdateCompanion::initAssetSettings() +void UpdateCompanion::assetSettingsInit() { - if (!isValidSettingsIndex()) + if (!isSettingsIndexValid()) return; g.component[id()].initAllAssets(); @@ -80,24 +80,24 @@ void UpdateCompanion::initAssetSettings() bool UpdateCompanion::asyncInstall() { #ifdef OS_SUPPORTED_INSTALLER - //reportProgress(tr("Run application installer: %1").arg(g.runAppInstaller() ? tr("true") : tr("false")), QtDebugMsg); + //status()->reportProgress(tr("Run application installer: %1").arg(g.runAppInstaller() ? tr("true") : tr("false")), QtDebugMsg); if (!g.runAppInstaller()) return true; - progressMessage(tr("Install")); + status()->progressMessage(tr("Install")); - assets->setFilterFlags(UPDFLG_AsyncInstall); - //reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_AsyncInstall)).arg(assets->count()), QtDebugMsg); + repo()->assets()->setFilterFlags(UPDFLG_AsyncInstall); + status()->reportProgress(tr("Asset filter applied: %1 - %2 found").arg(updateFlagToString(UPDFLG_AsyncInstall)).arg(repo()->assets()->count()), QtDebugMsg); - if (assets->count() != 1) { - reportProgress(tr("Expected 1 asset for install but none found"), QtCriticalMsg); + if (repo()->assets()->count() != 1) { + status()->reportProgress(tr("Expected 1 asset for install but none found"), QtCriticalMsg); return false; } - assets->getSetId(0); + repo()->assets()->getSetId(0); - QString installerPath = decompressDir % QString("/A%1/%2").arg(assets->id()).arg(QFileInfo(assets->filename()).completeBaseName()); + QString installerPath = decompressDir() % QString("/A%1/%2").arg(repo()->assets()->id()).arg(QFileInfo(repo()->assets()->name()).completeBaseName()); QDirIterator it(installerPath, { OS_INSTALLER_EXTN }, QDir::Files, QDirIterator::Subdirectories); @@ -111,14 +111,14 @@ bool UpdateCompanion::asyncInstall() } if (!found) { - reportProgress(tr("Installer not found in %1 using filter %2").arg(installerPath).arg(OS_INSTALLER_EXTN), QtCriticalMsg); + status()->reportProgress(tr("Installer not found in %1 using filter %2").arg(installerPath).arg(OS_INSTALLER_EXTN), QtCriticalMsg); return false; } #if defined(__linux__) || defined(__linux) QFile f(installerPath); if (!f.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner)) { - QMessageBox::critical(progress, CPN_STR_APP_NAME, tr("Unable to set file permissions on the AppImage")); + QMessageBox::critical(status()->progress(), CPN_STR_APP_NAME, tr("Unable to set file permissions on the AppImage")); return false; } @@ -131,24 +131,24 @@ bool UpdateCompanion::asyncInstall() if (dest.exists()) { if (!dest.remove()) { - QMessageBox::critical(progress, CPN_STR_APP_NAME, tr("Unable to delete file %1").arg(fileName)); + QMessageBox::critical(status()->progress(), CPN_STR_APP_NAME, tr("Unable to delete file %1").arg(fileName)); return false; } } if (!QFile::copy(installerPath, fileName)) { - QMessageBox::critical(progress, CPN_STR_APP_NAME, tr("Unable to copy file to %1").arg(fileName)); + QMessageBox::critical(status()->progress(), CPN_STR_APP_NAME, tr("Unable to copy file to %1").arg(fileName)); return false; } - if (QMessageBox::question(progress, CPN_STR_APP_NAME, "Restart Companion?", QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (QMessageBox::question(status()->progress(), CPN_STR_APP_NAME, "Restart Companion?", QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { QProcess::startDetached(fileName); qApp->exit(); } #else - int ret = QMessageBox::question(progress, CPN_STR_APP_NAME, OS_INSTALL_QUESTION, QMessageBox::Yes | QMessageBox::No); + int ret = QMessageBox::question(status()->progress(), CPN_STR_APP_NAME, OS_INSTALL_QUESTION, QMessageBox::Yes | QMessageBox::No); if (ret == QMessageBox::Yes) { if (QDesktopServices::openUrl(QUrl::fromLocalFile(installerPath))) @@ -160,23 +160,23 @@ bool UpdateCompanion::asyncInstall() return true; #else - QMessageBox::warning(progress, CPN_STR_APP_NAME, OS_INSTALL_MSG); + QMessageBox::warning(status()->progress, CPN_STR_APP_NAME, OS_INSTALL_MSG); return true; #endif // OS_SUPPORTED_INSTALLER } -const QString UpdateCompanion::currentRelease() +const QString UpdateCompanion::releaseCurrent() { #if defined(VERSION_TAG) - params->currentRelease = QString("EdgeTX \"%1\" %2").arg(CODENAME).arg(VERSION_TAG); + params()->releaseCurrent = QString("EdgeTX \"%1\" %2").arg(CODENAME).arg(VERSION_TAG); #else - params->currentRelease = QString("EdgeTX v%1.%2.%3-%4").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_REVISION).arg(VERSION_SUFFIX); + params()->releaseCurrent = QString("EdgeTX v%1.%2.%3-%4").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_REVISION).arg(VERSION_SUFFIX); #endif - return params->currentRelease; + return params()->releaseCurrent; } -const QString UpdateCompanion::currentVersion() +const QString UpdateCompanion::versionCurrent() { #if defined(VERSION_TAG) return VERSION_TAG; @@ -185,10 +185,10 @@ const QString UpdateCompanion::currentVersion() #endif } -const bool UpdateCompanion::isLatestRelease() +const bool UpdateCompanion::isReleaseLatest() { #if defined(VERSION_TAG) - return UpdateInterface::isLatestRelease(); + return UpdateInterface::isReleaseLatest(); #else return true; #endif diff --git a/companion/src/updates/updatecompanion.h b/companion/src/updates/updatecompanion.h index b7fad86e2..bfc0bae32 100644 --- a/companion/src/updates/updatecompanion.h +++ b/companion/src/updates/updatecompanion.h @@ -32,12 +32,12 @@ class UpdateCompanion : public UpdateInterface explicit UpdateCompanion(QWidget * parent); virtual ~UpdateCompanion() {} - protected: - virtual void initAssetSettings() override; - - virtual bool asyncInstall() override; - virtual const QString currentRelease() override; - virtual const QString currentVersion() override; - virtual const bool isLatestRelease() override; + virtual const QString releaseCurrent() override; + virtual const QString versionCurrent() override; + virtual const bool isReleaseLatest() override; virtual const bool isUpdateAvailable() override; + + protected: + virtual bool asyncInstall() override; + virtual void assetSettingsInit() override; }; diff --git a/companion/src/updates/updatefactories.cpp b/companion/src/updates/updatefactories.cpp new file mode 100644 index 000000000..b708bafed --- /dev/null +++ b/companion/src/updates/updatefactories.cpp @@ -0,0 +1,130 @@ +/* + * 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 "updatefactories.h" +#include "updatefirmware.h" +#include "updatecompanion.h" +#include "updatesdcard.h" +#include "updatesounds.h" +#include "updatethemes.h" +#include "updatemultiprotocol.h" + +UpdateFactories::UpdateFactories(QWidget * parent) : + QWidget(parent) +{ + registerUpdateFactories(); +} + +UpdateFactories::~UpdateFactories() +{ + unregisterUpdateFactories(); +} + +UpdateInterface* const UpdateFactories::instance(const int id) +{ + foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { + if (id == factory->id()) + return factory->instance(); + } + qDebug() << "Critical error - Interface not found for id:" << id; + return nullptr; +} + +const bool UpdateFactories::isUpdateAvailable(QMap & list) +{ + bool ret = false; + + list.clear(); + + foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { + if (factory->instance()->isUpdateable() && factory->instance()->isUpdateAvailable()) { + list.insert(factory->name(), factory->id()); + ret = true; + } + } + + return ret; +} + +void UpdateFactories::registerUpdateFactory(UpdateFactoryInterface * factory) +{ + foreach (UpdateFactoryInterface * registeredFactory, registeredUpdateFactories) { + if (registeredFactory->id() == factory->id()) { + qDebug() << "Duplicate factory - id:" << factory->id() << "name:" << factory->name(); + return; + } + } + registeredUpdateFactories.append(factory); + qDebug() << "Registered update factory:" << factory->name(); +} + +void UpdateFactories::registerUpdateFactories() +{ + // Note: register in logical updating sequence + // SDCard should be first as it may clean up/erase commmon destination directory + registerUpdateFactory(new UpdateFactory(this)); + registerUpdateFactory(new UpdateFactory(this)); + registerUpdateFactory(new UpdateFactory(this)); + registerUpdateFactory(new UpdateFactory(this)); + registerUpdateFactory(new UpdateFactory(this)); + + // Note: Companion must be last as its install requires the app to be closed and thus would interrupt the update loop + registerUpdateFactory(new UpdateFactory(this)); +} + +void UpdateFactories::resetAllEnvironments() +{ + foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { + factory->instance()->resetEnvironment(); + } +} + +const QMap UpdateFactories::sortedComponentsList(bool updateableOnly) +{ + QMap map; + + foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { + if (updateableOnly && !factory->instance()->isUpdateable()) + continue; + map.insert(factory->name(), factory->id()); + } + + return map; +} + +bool UpdateFactories::updateAll(ProgressWidget * progress) +{ + bool ret = false; + + foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { + ret = factory->instance()->update(progress); + if (!ret) + break; + } + + return ret; +} + +void UpdateFactories::unregisterUpdateFactories() +{ + foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) + delete factory; +} diff --git a/companion/src/updates/updatefactories.h b/companion/src/updates/updatefactories.h new file mode 100644 index 000000000..ab316fd8b --- /dev/null +++ b/companion/src/updates/updatefactories.h @@ -0,0 +1,77 @@ +/* + * 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 + +class UpdateFactoryInterface +{ + public: + explicit UpdateFactoryInterface() {} + virtual ~UpdateFactoryInterface() {} + + virtual const int id() = 0; + virtual UpdateInterface* const instance() = 0; + virtual const QString name() = 0; +}; + +template +class UpdateFactory : public UpdateFactoryInterface +{ + public: + explicit UpdateFactory(QWidget * parent) : + UpdateFactoryInterface(), + m_instance(new T(parent)) {} + + virtual ~UpdateFactory() {} + + virtual const int id() { return m_instance->id(); } + virtual UpdateInterface* const instance() { return m_instance; } + virtual const QString name() { return m_instance->name(); } + + private: + UpdateInterface* const m_instance; +}; + +class UpdateFactories : public QWidget +{ + Q_OBJECT + + public: + explicit UpdateFactories(QWidget * parent = nullptr); + virtual ~UpdateFactories(); + + UpdateInterface* const instance(const int id); + const bool isUpdateAvailable(QMap & names); + void registerUpdateFactories(); + void registerUpdateFactory(UpdateFactoryInterface * factory); + void resetAllEnvironments(); + const QMap sortedComponentsList(bool updateableOnly = false); + void unregisterUpdateFactories(); + bool updateAll(ProgressWidget * progress = nullptr); + + private: + QVector registeredUpdateFactories; +}; + diff --git a/companion/src/updates/updatefirmware.cpp b/companion/src/updates/updatefirmware.cpp index 4654ecd5d..2a1f4493b 100644 --- a/companion/src/updates/updatefirmware.cpp +++ b/companion/src/updates/updatefirmware.cpp @@ -23,14 +23,14 @@ #include "flashfirmwaredialog.h" UpdateFirmware::UpdateFirmware(QWidget * parent) : - UpdateInterface(parent) + UpdateInterface(parent, CID_Firmware, tr("Firmware")) { - init(CID_Firmware, tr("Firmware"), QString(GH_REPOS_EDGETX).append("/edgetx"), "nightly"); + init(QString(GH_API_REPOS_EDGETX).append("/edgetx"), "nightly"); } -void UpdateFirmware::initAssetSettings() +void UpdateFirmware::assetSettingsInit() { - if (!isValidSettingsIndex()) + if (!isSettingsIndexValid()) return; g.component[id()].initAllAssets(); @@ -44,7 +44,7 @@ void UpdateFirmware::initAssetSettings() cad.filter("edgetx-firmware"); cad.maxExpected(1); cad.destSubDir("FIRMWARE"); - cad.copyFilterType(UpdateParameters::UFT_Expression); + cad.copyFilterType(UpdateParameters::UFT_Pattern); cad.copyFilter("^%FWFLAVOUR%-.*\\.bin$"); qDebug() << "Asset settings initialised"; @@ -52,32 +52,32 @@ void UpdateFirmware::initAssetSettings() bool UpdateFirmware::asyncInstall() { - //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; - progressMessage(tr("Install")); + status()->progressMessage(tr("Install")); - assets->setFilterFlags(UPDFLG_AsyncInstall); - //reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_AsyncInstall)).arg(assets->count()), QtDebugMsg); + repo()->assets()->setFilterFlags(UPDFLG_AsyncInstall); + //status->reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_AsyncInstall)).arg(assets->count()), QtDebugMsg); - if (assets->count() < 1) + if (repo()->assets()->count() < 1) return true; - if (assets->count() != params->assets.at(0).maxExpected) { - reportProgress(tr("Expected %1 asset for install but %2 found").arg(params->assets.at(0).maxExpected).arg(assets->count()), QtCriticalMsg); + 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; + QString destPath = updateDir(); - if (!assets->subDirectory().isEmpty()) - destPath.append("/" % assets->subDirectory()); + if (!repo()->assets()->subDirectory().isEmpty()) + destPath.append("/" % repo()->assets()->subDirectory()); - const UpdateParameters::AssetParams &ap = params->assets.at(0); + const UpdateParameters::AssetParams &ap = params()->assets.at(0); - QString pattern(params->buildFilterPattern(ap.copyFilterType, ap.copyFilter)); + QString pattern(params()->buildFilterPattern(ap.copyFilterType, ap.copyFilter)); QRegularExpression filter(pattern, QRegularExpression::CaseInsensitiveOption); QDirIterator it(destPath); @@ -97,13 +97,13 @@ bool UpdateFirmware::asyncInstall() } if (!found) { - reportProgress(tr("Firmware not found in %1 using filter %2").arg(destPath).arg(filter.pattern()), QtCriticalMsg); + 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(progress, CPN_STR_APP_NAME, tr("Write the updated firmware to the radio now ?"), QMessageBox::Yes | QMessageBox::No); + 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(); diff --git a/companion/src/updates/updatefirmware.h b/companion/src/updates/updatefirmware.h index 0446aa439..29bda02f7 100644 --- a/companion/src/updates/updatefirmware.h +++ b/companion/src/updates/updatefirmware.h @@ -33,7 +33,6 @@ class UpdateFirmware : public UpdateInterface virtual ~UpdateFirmware() {} protected: - virtual void initAssetSettings() override; - virtual bool asyncInstall() override; + virtual void assetSettingsInit() override; }; diff --git a/companion/src/updates/updateinterface.cpp b/companion/src/updates/updateinterface.cpp index 1f8664e17..7779d16aa 100644 --- a/companion/src/updates/updateinterface.cpp +++ b/companion/src/updates/updateinterface.cpp @@ -20,7 +20,6 @@ */ #include "updateinterface.h" -#include "minizinterface.h" #include "helpers.h" #include "updatefirmware.h" #include "updatecompanion.h" @@ -28,134 +27,38 @@ #include "updatesounds.h" #include "updatethemes.h" #include "updatemultiprotocol.h" +#include "minizinterface.h" #include -#include -#include #include #include #include #include -/* - - UpdateParameters - -*/ - -UpdateParameters::UpdateParameters(QObject * parent) : - QObject(parent) -{ - logLevel = 0; - flags = 0; - decompressDirUseDwnld = true; - updateDirUseSD = true; -} - -UpdateParameters::AssetParams & UpdateParameters::addAsset() -{ - AssetParams ap; - ap.processes = 0; - ap.flags = 0; - ap.filterType = UFT_None; - ap.maxExpected = 0; - ap.copyFilterType = UFT_None; - assets.append(ap); - - return assets[assets.size() - 1]; -} - -QString UpdateParameters::buildFilterPattern(const UpdateFilterType filterType, const QString & filter) -{ - QString pattern(filter); - - pattern.replace("%FWFLAVOUR%", fwFlavour); - pattern.replace("%LANGUAGE%", language); - - switch ((int)filterType) { - case UFT_Exact: - pattern.prepend("^"); - pattern.append("$"); - break; - case UFT_Startswith: - pattern.prepend("^"); - break; - case UFT_Endswith: - pattern.append("$"); - break; - case UFT_Contains: - case UFT_Expression: - break; - default: - pattern = ""; - } - - return pattern; -} - -// static -QStringList UpdateParameters::updateFilterTypeList() -{ - return { tr("None"), tr("Exact"), tr("Startswith"), tr("Endswith"), tr("Contains"), tr("Expression") }; -} - -// static -QString UpdateParameters::updateFilterTypeToString(UpdateFilterType uft) -{ - return updateFilterTypeList().value(uft, CPN_STR_UNKNOWN_ITEM); -} - -/* - - UpdateInterface - -*/ - -UpdateInterface::UpdateInterface(QWidget * parent) : +UpdateInterface::UpdateInterface(QWidget * parent, ComponentIdentity id, QString name) : QWidget(parent), - progress(nullptr), - reply(nullptr), - buffer(new QByteArray()), - file(nullptr), - m_id(CID_Unknown), - m_name("") + 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)) { - QNetworkProxyFactory::setUseSystemConfiguration(true); - releases = new ReleasesMetaData(this); - assets = new AssetsMetaData(this); - params = new UpdateParameters(this); } UpdateInterface::~UpdateInterface() { - if (reply) - delete reply; - delete buffer; - if (file) - delete file; - delete releases; - delete assets; - delete params; + delete m_params; + delete m_repo; + delete m_network; + delete m_status; } -void UpdateInterface::init(ComponentIdentity id, QString name, QString repo, QString nightly, int resultsPerPage) +void UpdateInterface::appSettingsInit() { - setId(id); - setName(name); - - initAppSettings(); - - releases->init(repo, nightly, resultsPerPage, id); - assets->init(repo, resultsPerPage); - - currentRelease(); -} - -void UpdateInterface::initAppSettings() -{ - if (!isValidSettingsIndex()) { - reportProgress(tr("Component id: %1 exceeds maximum application settings components: %2!").arg(m_id).arg(MAX_COMPONENTS), QtCriticalMsg); + if (!isSettingsIndexValid()) { + m_status->reportProgress(tr("Component id: %1 exceeds maximum application settings components: %2!").arg(m_id).arg(MAX_COMPONENTS), QtCriticalMsg); return; } @@ -164,18 +67,18 @@ void UpdateInterface::initAppSettings() } if (!g.component[m_id].asset[0].existsOnDisk()) - initAssetSettings(); + assetSettingsInit(); } -void UpdateInterface::loadAssetSettings() +void UpdateInterface::assetSettingsLoad() { - if (!isValidSettingsIndex()) + if (!isSettingsIndexValid()) return; - params->assets.clear(); + m_params->assets.clear(); for (int i = 0; i < MAX_COMPONENT_ASSETS && g.component[m_id].asset[i].existsOnDisk(); i++) { - UpdateParameters::AssetParams &ap = params->addAsset(); + UpdateParameters::AssetParams &ap = m_params->addAsset(); ComponentAssetData &cad = g.component[m_id].asset[i]; ap.processes = cad.processes(); @@ -189,13 +92,13 @@ void UpdateInterface::loadAssetSettings() } } -void UpdateInterface::saveAssetSettings() +void UpdateInterface::assetSettingsSave() { - if (!isValidSettingsIndex()) + if (!isSettingsIndexValid()) return; for (int i = 0; i < MAX_COMPONENT_ASSETS && g.component[m_id].asset[i].existsOnDisk(); i++) { - const UpdateParameters::AssetParams &ap = params->assets.at(i); + const UpdateParameters::AssetParams &ap = m_params->assets.at(i); ComponentAssetData &cad = g.component[m_id].asset[i]; // DO NOT overwrite cad.processes @@ -209,69 +112,899 @@ void UpdateInterface::saveAssetSettings() } } -bool UpdateInterface::update(ProgressWidget * progress) +bool UpdateInterface::asyncInstall() { - if (!(params->flags & UPDFLG_Update)) - return true; + return true; +} - this->progress = progress; - - if (progress) { - progress->setInfo(tr("Processing updates for: %1").arg(m_name)); - progress->setValue(0); - progress->setMaximum(100); +bool UpdateInterface::checkCreateDirectory(const QString & dir, const UpdateFlags flag) +{ + if (m_params->flags & flag) { + if (dir.isEmpty()) { + m_status->reportProgress(tr("%1 directory not configured in application settings!").arg(updateFlagToString(flag)), QtCriticalMsg); + return false; + } + else if (!QDir().mkpath(dir)) { + m_status->reportProgress(tr("Failed to create %1 directory %2!").arg(updateFlagToString(flag)).arg(dir), QtCriticalMsg); + return false; + } } - reportProgress(tr("Processing updates for: %1").arg(m_name), QtInfoMsg); - - if (!preparation()) { - reportProgress(tr("%1 preparation failed").arg(m_name), QtCriticalMsg); - return false; - } - - if (!download()) { - reportProgress(tr("%1 download failed").arg(m_name), QtCriticalMsg); - return false; - } - - if (!decompress()) { - reportProgress(tr("%1 decompress failed").arg(m_name), QtCriticalMsg); - return false; - } - - if (!copyToDestination()) { - reportProgress(tr("%1 copy to destination failed").arg(m_name), QtCriticalMsg); - return false; - } - - // perform before async install in case Companion restarted - if (!saveReleaseSettings()) { - reportProgress(tr("Failed to save release settings"), QtDebugMsg); - return false; - } - - if (!asyncInstall()) { - reportProgress(tr("%1 start async failed").arg(m_name), QtCriticalMsg); - return false; - } - - if (!housekeeping()) { - reportProgress(tr("%1 housekeeping failed").arg(m_name), QtCriticalMsg); - return false; - } - - reportProgress(tr("%1 update successful").arg(m_name), QtInfoMsg); - - if (!progress) - QMessageBox::information(progress, CPN_STR_APP_NAME, tr("%1 update successful").arg(m_name)); - return true; } -// static -QString UpdateInterface::updateFlagsToString(UpdateFlags val) +bool UpdateInterface::copyAsset() { - switch ((int)val) { + if (m_repo->assets()->flags() & UPDFLG_CopyStructure) + return copyStructure(); + else if (m_repo->assets()->flags() & UPDFLG_CopyFiles) + return copyFiles(); + + return true; +} + +bool UpdateInterface::copyFiles() +{ + //m_status->reportProgress(tr("Copy files"), QtDebugMsg); + m_status->setValue(0); + m_status->setMaximum(100); + + QString srcPath; + + if (m_repo->assets()->flags() & UPDFLG_Decompress) + srcPath = QString("%1/A%2/%3").arg(m_decompressDir).arg(m_repo->assets()->id()).arg(QFileInfo(m_repo->assets()->name()).completeBaseName()); + else if (m_repo->assets()->flags() & UPDFLG_Download) + srcPath = QString("%1/A%2").arg(m_downloadDir).arg(m_repo->assets()->id()); + else { + m_status->reportProgress(tr("Unable to determine source directory for asset %1").arg(m_repo->assets()->name()), QtCriticalMsg); + return false; + } + + m_status->reportProgress(tr("Copy files from %1").arg(srcPath), QtDebugMsg); + + QString destPath = m_updateDir; + + if (!m_repo->assets()->subDirectory().isEmpty()) + destPath.append("/" % m_repo->assets()->subDirectory()); + + if (!QDir().mkpath(destPath)) { + m_status->reportProgress(tr("Failed to create directory %1").arg(QDir::toNativeSeparators(destPath)), QtCriticalMsg); + return false; + } + + m_status->reportProgress(tr("Check/create directory: %1").arg(destPath), QtDebugMsg); + m_status->reportProgress(tr("Directories checked/created: %1").arg(1), QtDebugMsg); + + QRegularExpression filter(m_repo->assets()->copyFilter(), QRegularExpression::CaseInsensitiveOption); + m_status->reportProgress(tr("Copy filter pattern: %1").arg(filter.pattern()), QtDebugMsg); + + QDirIterator itcnt(srcPath, QDir::Files, QDirIterator::Subdirectories); + + int cnt = 0; + + //m_status->reportProgress(tr("Calculating number of files"), QtDebugMsg); + + while (itcnt.hasNext()) + { + QFileInfo src(itcnt.next()); + + if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter))) + continue; + + cnt++; + } + + m_status->setMaximum(cnt); + + QDirIterator itfiles(srcPath, QDir::Files, QDirIterator::Subdirectories); + + cnt = 0; + + while (itfiles.hasNext()) + { + QFileInfo src(itfiles.next()); + + if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter))) + continue; + + QFile dest(destPath % "/" % src.fileName()); + + if (dest.exists()) { + if (!dest.remove()) { + m_status->reportProgress(tr("Failed to delete existing file %1").arg(dest.fileName()), QtCriticalMsg); + return false; + } + m_status->reportProgress(tr("Deleted file: %1").arg(dest.fileName()), QtDebugMsg); + } + + if (!QFile::copy(src.absoluteFilePath(), dest.fileName())) { + m_status->reportProgress(tr("Failed to copy file %1").arg(src.absoluteFilePath()), QtCriticalMsg); + return false; + } + + m_status->reportProgress(tr("Copied %1 to %2").arg(src.fileName()).arg(dest.fileName()), QtDebugMsg); + cnt++; + m_status->setValue(cnt); + } + + if (!cnt) { + m_status->reportProgress(tr("No downloaded or decompressed files matched filter '%1'").arg(m_repo->assets()->copyFilter()), QtCriticalMsg); + return false; + } + + m_status->setValue(m_status->maximum()); + m_status->reportProgress(tr("Files copied: %1").arg(cnt), QtInfoMsg); + + return true; +} + +bool UpdateInterface::copyFlaggedAssets() +{ + m_repo->assets()->setFilterFlags(UPDFLG_CopyDest); + m_status->reportProgress(tr("Asset filter applied: %1 - %2 found").arg(updateFlagToString(UPDFLG_CopyDest)).arg(m_repo->assets()->count()), QtDebugMsg); + + for (int i = 0; i < m_repo->assets()->count(); ++i) { + m_repo->assets()->getSetId(i); + if (!copyAsset()) + return false; + } + + return true; +} + +bool UpdateInterface::copyStructure() +{ + m_status->reportProgress(tr("Copy directory structure"), QtDebugMsg); + m_status->setValue(0); + m_status->setMaximum(100); + + QDir srcPath(QString("%1/A%2/%3").arg(m_decompressDir).arg(m_repo->assets()->id()).arg(QFileInfo(m_repo->assets()->name()).completeBaseName())); + + if (!srcPath.exists()) { + m_status->reportProgress(tr("Decompressed file structure not found at %1").arg(QDir::toNativeSeparators(srcPath.path())), QtCriticalMsg); + return false; + } + + m_status->reportProgress(tr("Copy file structure from: %1").arg(QDir::toNativeSeparators(srcPath.path())), QtDebugMsg); + + QString baseDir = m_updateDir; + + if (!m_repo->assets()->subDirectory().isEmpty()) + baseDir.append("/" % m_repo->assets()->subDirectory()); + + // create directory structure first as there is no order to QDirIterator if both files and directories selected + // also supports creation of empty directories + QDirIterator itdirs(srcPath.path(), QDir::AllDirs | QDir::NoDot | QDir::NoDotDot, QDirIterator::Subdirectories); + + int cnt = 0; + + while (itdirs.hasNext()) + { + QFileInfo src(itdirs.next()); + + QString subPath(src.absoluteFilePath()); + subPath.replace(srcPath.path(), ""); + + QString dir = baseDir % subPath; + + if (!QDir().mkpath(dir)) { + m_status->reportProgress(tr("Failed to create directory %1").arg(dir), QtCriticalMsg); + return false; + } + + m_status->reportProgress(tr("Check/create directory: %1").arg(dir), QtDebugMsg); + cnt++; + } + + m_status->reportProgress(tr("Directories checked/created: %1").arg(cnt), QtInfoMsg); + + QRegularExpression filter(m_repo->assets()->copyFilter(), QRegularExpression::CaseInsensitiveOption); + //m_status->reportProgress(tr("Copy filter pattern: %1").arg(filter.pattern()), QtDebugMsg); + + QDirIterator itcnt(srcPath.path(), QDir::Files, QDirIterator::Subdirectories); + + cnt = 0; + + //m_status->reportProgress(tr("Calculating number of files"), QtDebugMsg); + + while (itcnt.hasNext()) + { + QFileInfo src(itcnt.next()); + + if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter))) + continue; + + cnt++; + } + + m_status->setMaximum(cnt); + + QDirIterator itfiles(srcPath.path(), QDir::Files, QDirIterator::Subdirectories); + + cnt = 0; + + while (itfiles.hasNext()) + { + QFileInfo src(itfiles.next()); + + if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter))) + continue; + + QString subPath(src.absoluteFilePath()); + subPath.replace(srcPath.path(), ""); + + QFile dest(baseDir % subPath); + + if (dest.exists()) { + if (!dest.remove()) { + m_status->reportProgress(tr("Failed to delete existing file %1").arg(dest.fileName()), QtCriticalMsg); + return false; + } + } + + if (!QFile::copy(src.absoluteFilePath(), dest.fileName())) { + m_status->reportProgress(tr("Failed to copy file %1").arg(src.absoluteFilePath()), QtCriticalMsg); + return false; + } + + m_status->reportProgress(tr("Copied %1 to %2").arg(src.fileName()).arg(dest.fileName()), QtDebugMsg); + cnt++; + m_status->setValue(cnt); + } + + m_status->setValue(m_status->maximum()); + m_status->reportProgress(tr("Files copied: %1").arg(cnt), QtInfoMsg); + + return true; +} + +bool UpdateInterface::copyToDestination() +{ + m_status->progressMessage(tr("Copying to destination")); + + if (!copyFlaggedAssets()) { + m_status->reportProgress(tr("Unable to copy assets"), QtDebugMsg); + return false; + } + + return true; +} + +const QString UpdateInterface::decompressDir() const +{ + return m_decompressDir; +} + +bool UpdateInterface::decompressFlaggedAssets() +{ + m_repo->assets()->setFilterFlags(UPDFLG_Decompress); + m_status->reportProgress(tr("Asset filter applied: %1 - %2 found").arg(updateFlagToString(UPDFLG_Decompress)).arg(m_repo->assets()->count()), QtDebugMsg); + + for (int i = 0; i < m_repo->assets()->count(); ++i) { + if (!decompressAsset(i)) + return false; + } + + return true; +} + +bool UpdateInterface::decompressAsset(int row) +{ + m_repo->assets()->getSetId(row); + + if (!(m_repo->assets()->flags() & UPDFLG_Decompress)) + return true; + + QString filename = m_repo->assets()->name(); + + if (filename.isEmpty()) + return false; + + QFileInfo f(QString("%1/A%2/%3").arg(m_downloadDir).arg(m_repo->assets()->id()).arg(filename)); + + if (!f.exists()) + return false; + + m_status->progressMessage(tr("Decompressing %1").arg(filename)); + m_status->reportProgress(tr("Decompress: %1").arg(filename), QtInfoMsg); + + return decompressArchive(f.absoluteFilePath(), QString("%1/A%2").arg(m_decompressDir).arg(m_repo->assets()->id())); +} + +bool UpdateInterface::decompressArchive(const QString & archivePath, const QString & destDir) +{ + QString dest = destDir; + + if (!dest.endsWith("/")) + dest.append("/"); + + dest.append(QFileInfo(archivePath).completeBaseName()); + + MinizInterface mIface(m_status->progress(), MinizInterface::PCM_SIZE, m_params->logLevel); + + if (!mIface.unzipArchiveToPath(archivePath, dest)) { + m_status->reportProgress(tr("Failed to decompress %1").arg(archivePath), QtCriticalMsg); + return false; + } + + return true; +} + +const QString UpdateInterface::downloadDir() const +{ + return m_downloadDir; +} + +bool UpdateInterface::downloadFlaggedAssets() +{ + m_repo->assets()->setFilterFlags(UPDFLG_Download); + m_status->reportProgress(tr("Asset filter applied: %1 - %2 found").arg(updateFlagToString(UPDFLG_Download)).arg(m_repo->assets()->count()), QtDebugMsg); + + for (int i = 0; i < m_repo->assets()->count(); ++i) { + if (!m_repo->assets()->downloadToFile(i, m_downloadDir)) + return false; + } + + return true; +} + +bool UpdateInterface::download() +{ + m_status->progressMessage(tr("Downloading assets")); + + if (!downloadFlaggedAssets()) { + m_status->reportProgress(tr("Unable to download flagged assets"), QtDebugMsg); + return false; + } + + 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::flagAssets() +{ + m_status->progressMessage(tr("Flagging assets")); + + for (int i = 0; i < m_params->assets.size(); i++) { + const UpdateParameters::AssetParams & ap = m_params->assets.at(i); + if (!setFilteredAssets(ap)) + return false; + } + + return true; +} + +bool UpdateInterface::housekeeping() +{ + m_status->progressMessage(tr("Housekeeping")); + int cnt = 0; + + m_status->setValue(cnt); + m_status->setMaximum(2); + + m_status->setValue(++cnt); + + g.lastUpdateDir(m_params->updateDir); // used by sdsync + + if (m_params->flags & UPDFLG_DelDownloads) { + m_status->reportProgress(tr("Delete download directory: %1").arg(m_downloadDir), QtDebugMsg); + QDir d(m_downloadDir); + if (!d.removeRecursively()) + m_status->reportProgress(tr("Failed to delete downloads folder %1").arg(m_downloadDir), QtCriticalMsg); + } + + m_status->setValue(++cnt); + + if (m_params->flags & UPDFLG_DelDecompress) { + if (!m_params->decompressDirUseDwnld) { + m_status->reportProgress(tr("Delete decompress directory: %1").arg(m_decompressDir), QtDebugMsg); + QDir d(m_decompressDir); + if (!d.removeRecursively()) + m_status->reportProgress(tr("Failed to delete decompress folder %1").arg(m_decompressDir), QtCriticalMsg); + } + else if (!(m_params->flags & UPDFLG_DelDownloads)) { + m_repo->assets()->setFilterFlags(UPDFLG_Decompress); + for (int i = 0; i < m_repo->assets()->count(); i++) { + m_repo->assets()->getSetId(i); + QString path = QString("%1/A%2/%3").arg(m_decompressDir).arg(m_repo->assets()->id()).arg(QFileInfo(m_repo->assets()->name()).completeBaseName()); + m_status->reportProgress(tr("Delete decompress folder: %1").arg(path), QtDebugMsg); + if (!QDir(path).removeRecursively()) + m_status->reportProgress(tr("Failed to delete decompress folder %1").arg(path), QtCriticalMsg); + } + } + } + + m_status->setValue(++cnt); + + return true; +} + +const int UpdateInterface::id() const +{ + return (int)m_id; +} + +void UpdateInterface::init(QString repoPath, QString nightly, int resultsPerPage) +{ + appSettingsInit(); + m_repo->init(repoPath, nightly, resultsPerPage); + releaseCurrent(); +} + +const bool UpdateInterface::isReleaseLatest() +{ + if (!m_repo->releases()->retrieveMetaDataAll()) + return false; + + // release metadata and variables are cached so ensure referencing latest release for channel + m_repo->releases()->getSetId(); + + // nightlies often have the same version so also check id + if (isVersionLatest(versionCurrent(), m_repo->releases()->version()) && + g.component[m_id].releaseId() == m_repo->releases()->id()) + return true; + + return false; +} + +const bool UpdateInterface::isSettingsIndexValid() const +{ + return m_id > -1 && m_id < MAX_COMPONENTS; +} + +const bool UpdateInterface::isUpdateAvailable() +{ + if (g.component[m_id].checkForUpdate()) + return !isReleaseLatest(); + else { + return false; + } +} + +const bool UpdateInterface::isVersionLatest(const QString & installed, const QString & latest) +{ + QStringList a = versionToStringList(installed); + QStringList b = versionToStringList(latest); + + // not equal comparision used as version identifiers are inconsistent eg release vs nightly vs older + for (int i = 0; i < 4; i++) { + if (a.at(i) != b.at(i)) { + return false; + } + } + + return true; +} + +const bool UpdateInterface::isUpdateable() const +{ + if (!isSettingsIndexValid()) + return false; + + return g.component[m_id].checkForUpdate(); +} + +const QString UpdateInterface::releaseLatest() +{ + if (!m_repo->releases()->retrieveMetaDataAll()) + return tr("unknown"); + + return m_repo->releases()->name(); +} + +const QString UpdateInterface::name() const +{ + return m_name; +} + +UpdateNetwork* const UpdateInterface::network() const +{ + return m_network; +} + +UpdateParameters* const UpdateInterface::params() const +{ + return m_params; +} + +bool UpdateInterface::preparation() +{ + m_status->progressMessage(tr("Preparing")); + int cnt = 0; + + m_status->setValue(cnt); + m_status->setMaximum(5); + + if (!setRunFolders()) { + m_status->reportProgress(tr("Set run folders failed"), QtDebugMsg); + return false; + } + + m_status->setValue(++cnt); + + if (!m_repo->releases()->retrieveMetaDataAll()) { + return false; + } + + m_status->setValue(++cnt); + + if (!m_repo->releases()->getSetId(m_params->releaseUpdate)) { + m_status->reportProgress(tr("Set release id from update release '%1' failed").arg(m_params->releaseUpdate), QtCriticalMsg); + return false; + } + + m_status->setValue(++cnt); + + if (!m_repo->assets()->retrieveMetaDataAll()) + return false; + + m_status->setValue(++cnt); + + if (!flagAssets()) + return false; + + m_status->setValue(++cnt); + + return true; +} + +void UpdateInterface::releaseClear() +{ + g.component[m_id].releaseClear(); + releaseCurrent(); +} + +const QString UpdateInterface::releaseCurrent() +{ + m_params->releaseCurrent = g.component[m_id].release(); + return m_params->releaseCurrent; +} + +const QStringList UpdateInterface::releaseList() +{ + if (!m_repo->releases()->retrieveMetaDataAll()) + return QStringList(); + + return m_repo->releases()->list(); +} + +bool UpdateInterface::releaseSettingsSave() +{ + m_status->reportProgress(tr("Save release settings"), QtDebugMsg); + g.component[m_id].release(m_repo->releases()->name()); + g.component[m_id].version(m_repo->releases()->version()); + g.component[m_id].releaseId(m_repo->releases()->id()); + g.component[m_id].date(m_repo->releases()->date()); + + return true; +} + +const QString UpdateInterface::releaseUpdate() +{ + if (m_params->releaseUpdate.isEmpty()) + m_params->releaseUpdate = releaseLatest(); + + return m_params->releaseUpdate; +} + +Repo* const UpdateInterface::repo() const +{ + return m_repo; +} + +void UpdateInterface::resetEnvironment() +{ + // reset from previous run if any + m_params->flags &= ~UPDFLG_Update; + m_params->logLevel = g.updLogLevel(); + + m_status->setProgress(nullptr); + m_status->setLogLevel(m_params->logLevel); + + setReleaseChannel(g.component[m_id].releaseChannel()); + m_params->releaseUpdate = ""; + setFirmwareFlavour(); + setLanguage(); + assetSettingsLoad(); + setParamFolders(); + + if (g.updDelDownloads()) + m_params->flags |= UpdateInterface::UPDFLG_DelDownloads; + else + m_params->flags &= ~UpdateInterface::UPDFLG_DelDownloads; + + if (g.updDelDecompress()) + m_params->flags |= UpdateInterface::UPDFLG_DelDecompress; + else + m_params->flags &= ~UpdateInterface::UPDFLG_DelDecompress; +} + +bool UpdateInterface::retrieveAssetsJsonFile(const QString & assetName, QJsonDocument * json) +{ + m_repo->assets()->setFilterPattern(assetName); + return m_repo->assets()->getJson(assetName, json); +} + +bool UpdateInterface::retrieveRepoJsonFile(const QString & filename, QJsonDocument * json) +{ + return m_repo->getJson(filename, json); +} + +bool UpdateInterface::setFilteredAssets(const UpdateParameters::AssetParams & ap) +{ + QString pattern(m_params->buildFilterPattern(ap.filterType, ap.filter)); + m_repo->assets()->setFilterPattern(pattern); + m_status->reportProgress(tr("Asset filter applied: %1 - %2 found").arg(pattern).arg(m_repo->assets()->count()), QtDebugMsg); + + if (m_repo->assets()->count() < 1) { + m_status->reportProgress(tr("No assets found in release '%1' using filter '%2'").arg(m_repo->releases()->name()).arg(pattern), QtCriticalMsg); + return false; + } + else if (ap.maxExpected > 0 && m_repo->assets()->count() > ap.maxExpected) { + m_status->reportProgress(tr("%1 assets found when %2 expected in release '%3' using filter '%4'") + .arg(m_repo->assets()->count() + 1).arg(ap.maxExpected).arg(m_repo->releases()->name().arg(pattern)), QtCriticalMsg); + return false; + } + + for (int i = 0; i < m_repo->assets()->count(); i++) { + m_repo->assets()->getSetId(i); + if (!m_repo->assets()->setFlags(ap.flags)) { + m_status->reportProgress(tr("Unable to set processing flags for asset %1").arg(m_repo->assets()->name()), QtCriticalMsg); + return false; + } + + if (!m_repo->assets()->setSubDirectory(ap.destSubDir)) { + m_status->reportProgress(tr("Unable to set sub directory for asset %1").arg(m_repo->assets()->name()), QtCriticalMsg); + return false; + } + + if (!m_repo->assets()->setCopyFilter(m_params->buildFilterPattern(ap.copyFilterType, ap.copyFilter))) { + m_status->reportProgress(tr("Unable to set copy filter for asset %1").arg(m_repo->assets()->name()), QtCriticalMsg); + return false; + } + } + + return true; +} + +void UpdateInterface::setFirmwareFlavour() +{ + const QStringList currVariant = getCurrentFirmware()->getId().split('-'); + + m_params->fwFlavour = ""; + + if (currVariant.size() > 1) { + m_params->fwFlavour = currVariant.at(1); + // Companion registered firmware identities (refer opentxinterface.cpp) do not always match the EdgeTX repo fw.json file + const QMap map = { + { "x7access", "x7-access" }, + { "x9d+", "x9dp" }, + { "x9d+2019", "x9dp2019" }, + { "x9ehall", "x9e-hall" }, + { "x10express", "x10-access" } + }; + QString repofw = map[m_params->fwFlavour]; + if (!repofw.isEmpty()) + m_params->fwFlavour = repofw; + } +} + +void UpdateInterface::setLanguage() +{ + const Firmware * baseFw = getCurrentFirmware()->getFirmwareBase(); + const QStringList currVariant = getCurrentFirmware()->getId().split('-'); + + m_params->language = ""; + + for (const char *lang : baseFw->languageList()) { + if (currVariant.last() == lang) { + m_params->language = currVariant.last(); + break; + } + } +} + +void UpdateInterface::setParamFolders() +{ + if (g.downloadDir().trimmed().isEmpty()) + g.downloadDirReset(true); + + m_params->downloadDir = g.downloadDir().trimmed(); + + m_params->decompressDirUseDwnld = g.decompressDirUseDwnld(); + + if (m_params->decompressDirUseDwnld) + m_params->decompressDir = m_params->downloadDir; + else { + if (g.decompressDir().trimmed().isEmpty()) + g.decompressDirReset(true); + + m_params->decompressDir = g.decompressDir().trimmed(); + } + + if (g.currentProfile().sdPath().trimmed().isEmpty()) + m_params->updateDirUseSD = false; + else + m_params->updateDirUseSD = g.updateDirUseSD(); + + if (m_params->updateDirUseSD) { + m_params->sdDir = g.currentProfile().sdPath().trimmed(); + m_params->updateDir = m_params->sdDir; + } + else { + if (g.updateDir().trimmed().isEmpty()) + g.updateDirReset(true); + + m_params->updateDir = g.updateDir().trimmed(); + } +} + +void UpdateInterface::setReleaseChannel(const int channel) +{ + m_params->releaseChannel = channel; + m_repo->releases()->setChannel(channel); +} + +void UpdateInterface::setReleaseId(QString name) +{ + m_repo->releases()->getSetId(name); +} + +bool UpdateInterface::setRunFolders() +{ + QRegularExpression rx("[0-9a-zA-Z_\\-.]+"); + // the validator treats the regexp as "^[0-9a-zA-Z_\-.]+$" + QRegularExpressionValidator v(rx); + int pos = 0; + + QString fldr(m_repo->releases()->name().trimmed()); + + if (v.validate(fldr, pos) != QValidator::Acceptable) { + fldr.replace("\"", ""); + fldr.replace("'", ""); + fldr.replace("(", "_"); + fldr.replace(")", "_"); + fldr.replace("[", "_"); + fldr.replace("]", "_"); + fldr.replace(" ", "_"); + fldr.replace("__", "_"); + + if (v.validate(fldr, pos) != QValidator::Acceptable) { + //m_status->reportProgress(tr("Unable to use release name %1 for directory name using default %2") + // .arg(m_repo->releases()->name().trimmed()).arg(QString("R%1").arg(m_repo->releases()->id())), QtDebugMsg); + fldr = QString("R%1").arg(m_repo->releases()->id()); + } + } + + m_downloadDir = QString("%1/%2/%3").arg(m_params->downloadDir).arg(m_name).arg(fldr); + + if (!checkCreateDirectory(m_downloadDir, UPDFLG_Download)) + return false; + + //m_status->reportProgress(tr("Download directory: %1").arg(m_downloadDir), QtDebugMsg); + + m_decompressDir = QString("%1/%2/%3").arg(m_params->decompressDir).arg(m_name).arg(fldr); + + if (!checkCreateDirectory(m_decompressDir, UPDFLG_Decompress)) + return false; + + //m_status->reportProgress(tr("Decompress directory: %1").arg(m_decompressDir), QtDebugMsg); + + m_updateDir = m_params->updateDir; + + if (!checkCreateDirectory(m_updateDir, UPDFLG_CopyDest)) + return false; + + //m_status->reportProgress(tr("Update directory: %1").arg(m_updateDir), QtDebugMsg); + + return true; +} + +void UpdateInterface::setRunUpdate() +{ + m_params->flags |= UPDFLG_Update; +} + +UpdateStatus* const UpdateInterface::status() const +{ + return m_status; +} + +bool UpdateInterface::update(ProgressWidget * progress) +{ + if (!(m_params->flags & UPDFLG_Update)) + return true; + + 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); + + if (!preparation()) { + m_status->reportProgress(tr("%1 preparation failed").arg(m_name), QtCriticalMsg); + return false; + } + + if (!download()) { + m_status->reportProgress(tr("%1 download failed").arg(m_name), QtCriticalMsg); + return false; + } + + if (!decompress()) { + m_status->reportProgress(tr("%1 decompress failed").arg(m_name), QtCriticalMsg); + return false; + } + + if (!copyToDestination()) { + m_status->reportProgress(tr("%1 copy to destination failed").arg(m_name), QtCriticalMsg); + return false; + } + + // perform before async install in case Companion restarted + if (!releaseSettingsSave()) { + m_status->reportProgress(tr("Failed to save release settings"), QtDebugMsg); + return false; + } + + if (!asyncInstall()) { + m_status->reportProgress(tr("%1 start async failed").arg(m_name), QtCriticalMsg); + return false; + } + + if (!housekeeping()) { + m_status->reportProgress(tr("%1 housekeeping failed").arg(m_name), QtCriticalMsg); + return false; + } + + m_status->reportProgress(tr("%1 update successful").arg(m_name), QtInfoMsg); + + if (!m_status->progress()) + QMessageBox::information(m_status->progress(), CPN_STR_APP_NAME, tr("%1 update successful").arg(m_name)); + + return true; +} + +const QString UpdateInterface::updateDir() const +{ + return m_updateDir; +} + +// static +const QString UpdateInterface::updateFlagsToString(const int flags) +{ + QString str; + + for (int i = 0; i < 32; i++) { + int flag = flags & (1 << i); + if (flag) { + if (!str.isEmpty()) + str.append(";"); + str.append(updateFlagToString(flag)); + } + } + + return str; +} + +// static +const QString UpdateInterface::updateFlagToString(const int flag) +{ + switch (flag) { case UPDFLG_None: return "None"; case UPDFLG_Update: @@ -296,27 +1029,19 @@ QString UpdateInterface::updateFlagsToString(UpdateFlags val) return "Async Install"; case UPDFLG_DelDownloads: return "Delete Downloads"; + case UPDFLG_DelDecompress: + return "Delete Decompress"; default: return CPN_STR_UNKNOWN_ITEM; } } -// static -QString UpdateInterface::downloadDataTypeToString(DownloadDataType val) +const QString UpdateInterface::versionCurrent() { - switch ((int)val) { - case DDT_Binary: - return "Binary"; - case DDT_Content: - return "Content"; - case DDT_MetaData: - return "Meta Data"; - default: - return CPN_STR_UNKNOWN_ITEM; - } + return g.component[m_id].version(); } -// static +// static QStringList UpdateInterface::versionToStringList(QString version) { QStringList strl = version.split("."); @@ -327,1348 +1052,3 @@ QStringList UpdateInterface::versionToStringList(QString version) return strl; } - -void UpdateInterface::reportProgress(const QString & text, const int type) -{ - if (params->logLevel == QtDebugMsg || - (params->logLevel == QtInfoMsg && type > QtDebugMsg) || - (type < QtInfoMsg && type >= params->logLevel)) { - if (progress) - progress->addMessage(text, type); - else - qDebug() << text; - } - - if (type == QtCriticalMsg || type == QtFatalMsg) - criticalMsg(text); -} - -void UpdateInterface::progressMessage(const QString & text) -{ - if (progress) - progress->setInfo(text); - else - qDebug() << text; -} - -void UpdateInterface::criticalMsg(const QString & msg) -{ - QMessageBox::critical(progress, tr("Update Interface"), msg); -} - -bool UpdateInterface::isUpdateable() -{ - return g.component[m_id].checkForUpdate(); -} - -void UpdateInterface::resetEnvironment() -{ - // reset from previous run if any - params->flags &= ~UPDFLG_Update; - progress = nullptr; - - params->logLevel = g.updLogLevel(); - setReleaseChannel(g.component[m_id].releaseChannel()); - params->updateRelease = ""; - setFlavourLanguage(); - loadAssetSettings(); - setParamFolders(); - - if (g.updDelDownloads()) - params->flags |= UpdateInterface::UPDFLG_DelDownloads; - else - params->flags &= ~UpdateInterface::UPDFLG_DelDownloads; - - if (g.updDelDecompress()) - params->flags |= UpdateInterface::UPDFLG_DelDecompress; - else - params->flags &= ~UpdateInterface::UPDFLG_DelDecompress; -} - -void UpdateInterface::setReleaseChannel(int channel) -{ - params->releaseChannel = channel; - //repoReleasesMetaData(); - releases->setReleaseChannel(channel); -} - -void UpdateInterface::setReleaseId(QString val) -{ - releases->getSetId(val); -} - -void UpdateInterface::setFlavourLanguage() -{ - const Firmware * baseFw = getCurrentFirmware()->getFirmwareBase(); - const QStringList currVariant = getCurrentFirmware()->getId().split('-'); - - params->fwFlavour = ""; - - if (currVariant.size() > 1) { - params->fwFlavour = currVariant.at(1); - // Companion registered firmware identities (refer opentxinterface.cpp) do not always match the EdgeTX repo fw.json file - const QMap map = { - { "x7access", "x7-access" }, - { "x9d+", "x9dp" }, - { "x9d+2019", "x9dp2019" }, - { "x9ehall", "x9e-hall" }, - { "x10express", "x10-access" } - }; - QString repofw = map[params->fwFlavour]; - if (!repofw.isEmpty()) - params->fwFlavour = repofw; - } - - params->language = ""; - - for (const char *lang : baseFw->languageList()) { - if (currVariant.last() == lang) { - params->language = currVariant.last(); - break; - } - } -} - -void UpdateInterface::setParamFolders() -{ - if (g.downloadDir().trimmed().isEmpty()) - g.downloadDirReset(true); - - params->downloadDir = g.downloadDir().trimmed(); - - params->decompressDirUseDwnld = g.decompressDirUseDwnld(); - - if (params->decompressDirUseDwnld) - params->decompressDir = params->downloadDir; - else { - if (g.decompressDir().trimmed().isEmpty()) - g.decompressDirReset(true); - - params->decompressDir = g.decompressDir().trimmed(); - } - - if (g.currentProfile().sdPath().trimmed().isEmpty()) - params->updateDirUseSD = false; - else - params->updateDirUseSD = g.updateDirUseSD(); - - if (params->updateDirUseSD) { - params->sdDir = g.currentProfile().sdPath().trimmed(); - params->updateDir = params->sdDir; - } - else { - if (g.updateDir().trimmed().isEmpty()) - g.updateDirReset(true); - - params->updateDir = g.updateDir().trimmed(); - } -} - -void UpdateInterface::clearRelease() -{ - g.component[m_id].clearRelease(); - currentRelease(); -} - -const QString UpdateInterface::currentRelease() -{ - params->currentRelease = g.component[m_id].release(); - return params->currentRelease; -} - -QString UpdateInterface::latestRelease() -{ - if (!repoReleasesMetaData()) - return tr("unknown"); - - return releases->name(); -} - -const QString UpdateInterface::updateRelease() -{ - if (params->updateRelease.isEmpty()) - params->updateRelease = latestRelease(); - - return params->updateRelease; -} - -const bool UpdateInterface::isUpdateAvailable() -{ - if (g.component[m_id].checkForUpdate()) - return !isLatestRelease(); - else { - return false; - } -} - -const bool UpdateInterface::isLatestVersion(const QString & installed, const QString & latest) -{ - QStringList a = versionToStringList(installed); - QStringList b = versionToStringList(latest); - - // not equal comparision used as version identifiers are inconsistent eg release vs nightly vs older - for (int i = 0; i < 4; i++) { - if (a.at(i) != b.at(i)) { - return false; - } - } - - return true; -} - -const bool UpdateInterface::isLatestRelease() -{ - if (!repoReleasesMetaData()) - return false; - - QString currentVer = currentVersion(); - QString latestVer = releases->version(); - // nightlies often have the same version so also check id - if (isLatestVersion(currentVer, latestVer) && g.component[m_id].releaseId() == releases->id()) { - return true; - } - else { - return false; - } -} - -const QString UpdateInterface::currentVersion() -{ - return g.component[m_id].version(); -} - -const QStringList UpdateInterface::getReleases() -{ - if (!repoReleasesMetaData()) - return QStringList(); - - return releases->list(); -} - -bool UpdateInterface::repoReleasesMetaData() -{ - if (releases->refreshRequired()) { - if (!downloadReleasesMetaData()) { - reportProgress(tr("Unable to download release meta data"), QtDebugMsg); - releases->setId(0); - return false; - } - - releases->getSetId(); - } - - return true; -} - -bool UpdateInterface::repoReleaseAssetsMetaData() -{ - if (!repoReleasesMetaData()) - return false; - - if (!downloadReleaseAssetsMetaData(releases->id())) { - reportProgress(tr("Unable to download release assets information"), QtDebugMsg); - return false; - } - - return true; -} - -bool UpdateInterface::setRunFolders() -{ - QRegExp rx("[0-9a-zA-Z_\\-.]+"); - // the validator treats the regexp as "^[0-9a-zA-Z_\-.]+$" - QRegExpValidator v(rx); - int pos = 0; - - QString fldr(releases->name().trimmed()); - - if (v.validate(fldr, pos) != QValidator::Acceptable) { - fldr.replace("\"", ""); - fldr.replace("'", ""); - fldr.replace("(", "_"); - fldr.replace(")", "_"); - fldr.replace("[", "_"); - fldr.replace("]", "_"); - fldr.replace(" ", "_"); - fldr.replace("__", "_"); - - if (v.validate(fldr, pos) != QValidator::Acceptable) { - //reportProgress(tr("Unable to use release name %1 for directory name using default %2") - // .arg(releases->name().trimmed()).arg(QString("R%1").arg(releases->id())), QtDebugMsg); - fldr = QString("R%1").arg(releases->id()); - } - } - - downloadDir = QString("%1/%2/%3").arg(params->downloadDir).arg(m_name).arg(fldr); - - if (!checkCreateDirectory(downloadDir, UPDFLG_Download)) - return false; - - //reportProgress(tr("Download directory: %1").arg(downloadDir), QtDebugMsg); - - decompressDir = QString("%1/%2/%3").arg(params->decompressDir).arg(m_name).arg(fldr); - - if (!checkCreateDirectory(decompressDir, UPDFLG_Decompress)) - return false; - - //reportProgress(tr("Decompress directory: %1").arg(decompressDir), QtDebugMsg); - - updateDir = params->updateDir; - - if (!checkCreateDirectory(updateDir, UPDFLG_CopyDest)) - return false; - - //reportProgress(tr("Update directory: %1").arg(updateDir), QtDebugMsg); - - return true; -} - -bool UpdateInterface::checkCreateDirectory(const QString & dir, const UpdateFlags flag) -{ - if (params->flags & flag) { - if (dir.isEmpty()) { - reportProgress(tr("%1 directory not configured in application settings!").arg(updateFlagsToString(flag)), QtCriticalMsg); - return false; - } - else if (!QDir().mkpath(dir)) { - reportProgress(tr("Failed to create %1 directory %2!").arg(updateFlagsToString(flag)).arg(dir), QtCriticalMsg); - return false; - } - } - - return true; -} - - bool UpdateInterface::getReleaseJsonAsset(const QString assetName, QJsonDocument * json) -{ - if (!repoReleaseAssetsMetaData()) - return false; - - if (!assets->getSetId(assetName)) - return false; - - if (!downloadAssetToBuffer(assets->id())) { - return false; - } - - if (!convertDownloadToJson(json)) { - return false; - } - - return true; -} - - bool UpdateInterface::getRepoJsonFile(const QString filename, QJsonDocument * json) -{ - if (!downloadTextFileToBuffer(filename)) { - return false; - } - - if (!convertDownloadToJson(json)) { - return false; - } - - return true; -} - -bool UpdateInterface::downloadReleasesMetaData() -{ - //progressMessage(tr("Download releases metadata")); - downloadMetaData(MDT_Releases, releases->urlReleases()); - return downloadSuccess; -} - -bool UpdateInterface::downloadReleaseLatestMetaData() -{ - //progressMessage(tr("Download latest release metadata")); - downloadMetaData(MDT_Release, releases->urlReleaseLatest()); - return downloadSuccess; -} - -bool UpdateInterface::downloadReleaseMetaData(const int releaseId) -{ - //progressMessage(tr("Download release %1 metadata").arg(releaseId)); - releases->setId(releaseId); - downloadMetaData(MDT_Release, releases->urlRelease()); - return downloadSuccess; -} - -bool UpdateInterface::downloadReleaseAssetsMetaData(const int releaseId) -{ - //progressMessage(tr("Download release %1 assets metadata").arg(releaseId)); - downloadMetaData(MDT_ReleaseAssets, assets->urlReleaseAssets(releaseId)); - return downloadSuccess; -} - -bool UpdateInterface::downloadAssetMetaData(const int assetId) -{ - //progressMessage(tr("Download asset %1 metadata").arg(assetId)); - assets->setId(assetId); - downloadMetaData(MDT_Asset, assets->urlAsset()); - return downloadSuccess; -} - -void UpdateInterface::downloadMetaData(const MetaDataType mdt, const QString & url) -{ - //reportProgress(tr("Download metadata for: %1").arg(metaDataTypeToString(mdt).toLower()), QtDebugMsg); - download(DDT_MetaData, mdt, url, GH_ACCEPT_HEADER_METADATA, QString()); -} - -bool UpdateInterface::downloadAssetToBuffer(const int assetId) -{ - //progressMessage(tr("Download asset %1").arg(assetId)); - assets->setId(assetId); - download(DDT_Content, 0, assets->urlAsset(), GH_ACCEPT_HEADER_BINARY, QString()); - return downloadSuccess; -} - -bool UpdateInterface::downloadTextFileToBuffer(const QString & path) -{ - //progressMessage(tr("Download file %1").arg(path)); - //reportProgress(tr("Download file: %1").arg(path), QtInfoMsg); - - downloadFileToBuffer(assets->urlContent(path)); - return downloadSuccess; -} - -void UpdateInterface::downloadFileToBuffer(const QString & url) -{ - download(DDT_Content, 0, url, GH_ACCEPT_HEADER_RAW, QString()); -} - -void UpdateInterface::download(const DownloadDataType type, const int subtype, const QString & urlStr, - const char * header, const QString & filePath) -{ - if (progress) { - progress->setValue(0); - progress->setMaximum(100); - } - - buffer->clear(); - downloadSuccess = false; - - if (type == DDT_SaveToFile) { - file = new QFile(filePath); - if (!file->open(QIODevice::WriteOnly)) { - reportProgress(tr("Unable to open the download file %1 for writing.\nError: %2").arg(filePath).arg(file->errorString()), QtCriticalMsg); - return; - } - } - else - file = nullptr; - - url.setUrl(urlStr); - - if (!url.isValid()) { - reportProgress(tr("Invalid URL: %1").arg(urlStr), QtCriticalMsg); - return; - } - else - reportProgress(tr("URL: %1").arg(urlStr), QtDebugMsg); - - request.setUrl(url); - request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork); - request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); - - request.setRawHeader(QByteArray("Accept"), QByteArray(header)); - - reply = manager.get(request); - - connect(reply, QOverload::of(&QNetworkReply::error), [=] (QNetworkReply::NetworkError code) { - // leave it to the finished slot to deal with error condition - reportProgress(tr("Network error has occurred. Error code: %1").arg(code), QtDebugMsg); - }); - - connect(reply, &QNetworkReply::readyRead, [=]() { - if (type == DDT_SaveToFile) { - file->write(reply->readAll()); - } - else { - const QByteArray qba = reply->readAll(); - buffer->append(qba); - } - }); - - connect(reply, &QNetworkReply::finished, [&]() { - onDownloadFinished(reply, type, subtype); - }); - - connect(reply, &QNetworkReply::downloadProgress, [=](const qint64 bytesRead, const qint64 totalBytes) { - if (progress) { - progress->setMaximum(totalBytes); - progress->setValue(bytesRead); - } - }); - - while (!reply->isFinished()) { - qApp->processEvents(); - } -} - -void UpdateInterface::onDownloadFinished(QNetworkReply * reply, DownloadDataType type, int subtype) -{ - if (type == DDT_SaveToFile) { - file->flush(); - file->close(); - } - - if (progress) - progress->setValue(progress->maximum()); - - if (reply->error()) { - reportProgress(tr("Unable to download %1.\nError:%2\n%3").arg(downloadDataTypeToString(type)).arg(reply->error()).arg(reply->errorString()), QtCriticalMsg); - - if (type == DDT_SaveToFile) { - file->remove(); - delete file; - file = nullptr; - } - - return; - } - - if (type == DDT_MetaData) { - parseMetaData(subtype); - } - else if (type == DDT_SaveToFile) { - delete file; - file = nullptr; - downloadSuccess = true; - } - else { - downloadSuccess = true; - } -} - -void UpdateInterface::parseMetaData(int mdt) -{ - //reportProgress(tr("Parse and load data models with metadata"), QtDebugMsg); - - QJsonDocument *json = new QJsonDocument(); - - if (!convertDownloadToJson(json)) { - return; - } - - switch (mdt) { - case MDT_Release: - case MDT_Releases: - releases->parseMetaData(mdt, json); - break; - case MDT_Asset: - case MDT_ReleaseAssets: - assets->parseMetaData(mdt, json); - break; - } - - delete json; - downloadSuccess = true; -} - -bool UpdateInterface::convertDownloadToJson(QJsonDocument * json) -{ - QJsonParseError res; - - *json = QJsonDocument::fromJson(*buffer, &res); - - if (res.error || json->isNull()) { - reportProgress(tr("Unable to convert downloaded data to json format.\nError:%1\n%2").arg(res.error).arg(res.errorString()), QtCriticalMsg); - return false; - } - - return true; -} - -bool UpdateInterface::getSetAssets(const UpdateParameters::AssetParams & ap) -{ - QString pattern(params->buildFilterPattern(ap.filterType, ap.filter)); - assets->setFilterPattern(pattern); - reportProgress(tr("Asset filter: %1").arg(pattern), QtDebugMsg); - - if (assets->count() < 1) { - reportProgress(tr("No assets found in release '%1' using filter '%2'").arg(releases->name()).arg(pattern), QtCriticalMsg); - return false; - } - else if (ap.maxExpected > 0 && assets->count() > ap.maxExpected) { - reportProgress(tr("%1 assets found when %2 expected in release '%3' using filter '%4'") - .arg(assets->count() + 1).arg(ap.maxExpected).arg(releases->name().arg(pattern)), QtCriticalMsg); - return false; - } - - //reportProgress(tr("Assets found: %1").arg(assets->count()), QtDebugMsg); - - for (int i = 0; i < assets->count(); i++) { - assets->getSetId(i); - if (!assets->setFlags(ap.flags)) { - reportProgress(tr("Unable to set processing flags for asset %1").arg(assets->filename()), QtCriticalMsg); - return false; - } - - if (!assets->setSubDirectory(ap.destSubDir)) { - reportProgress(tr("Unable to set sub directory for asset %1").arg(assets->filename()), QtCriticalMsg); - return false; - } - - if (!assets->setCopyFilter(params->buildFilterPattern(ap.copyFilterType, ap.copyFilter))) { - reportProgress(tr("Unable to set copy filter for asset %1").arg(assets->filename()), QtCriticalMsg); - return false; - } - } - - return true; -} - -bool UpdateInterface::downloadFlaggedAssets() -{ - assets->setFilterFlags(UPDFLG_Download); - //reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_Download)).arg(assets->count()), QtDebugMsg); - - for (int i = 0; i < assets->count(); ++i) { - if (!downloadAsset(i)) - return false; - } - - return true; -} - -bool UpdateInterface::downloadAsset(int row) -{ - assets->getSetId(row); - downloadBinaryToFile(assets->urlAsset(), assets->filename()); - return downloadSuccess; -} - -void UpdateInterface::downloadBinaryToFile(const QString & url, const QString & filename) -{ - downloadSuccess = false; - - QFileInfo f(QString("%1/A%2/%3").arg(downloadDir).arg(assets->id()).arg(filename)); - - progressMessage(tr("Downloading: %1").arg(filename)); - reportProgress(tr("Download: %1").arg(filename), QtInfoMsg); - - if (f.exists()) { - reportProgress(tr("File exists: %1").arg(filename), QtDebugMsg); - if (progress) { - int reply = QMessageBox::question(progress, CPN_STR_APP_NAME, tr("File %1 exists. Download again?").arg(filename), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No); - if (reply == QMessageBox::Cancel) { - //reportProgress(tr("User action: Cancel"), QtDebugMsg); - downloadSuccess = false; - return; - } - else if (reply == QMessageBox::No) { - //reportProgress(tr("User action: Use previous download"), QtDebugMsg); - downloadSuccess = true; - return; - } - else { - ; - //reportProgress(tr("User action: Download again"), QtDebugMsg); - } - } - } - else if (!QDir().mkpath(f.path())) { - reportProgress(tr("Failed to create directory %1!").arg(f.path()), QtCriticalMsg); - return; - } - - download(DDT_SaveToFile, 0, url, GH_ACCEPT_HEADER_BINARY, f.absoluteFilePath()); -} - -bool UpdateInterface::decompressFlaggedAssets() -{ - assets->setFilterFlags(UPDFLG_Decompress); - //reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_Decompress)).arg(assets->count()), QtDebugMsg); - - for (int i = 0; i < assets->count(); ++i) { - if (!decompressAsset(i)) - return false; - } - - return true; -} - -bool UpdateInterface::decompressAsset(int row) -{ - assets->getSetId(row); - - if (!(assets->flags() & UPDFLG_Decompress)) - return true; - - QString filename = assets->filename(); - - if (filename.isEmpty()) - return false; - - QFileInfo f(QString("%1/A%2/%3").arg(downloadDir).arg(assets->id()).arg(assets->filename())); - - if (!f.exists()) - return false; - - progressMessage(tr("Decompressing %1").arg(filename)); - reportProgress(tr("Decompress: %1").arg(filename), QtInfoMsg); - - return decompressArchive(f.absoluteFilePath(), QString("%1/A%2").arg(decompressDir).arg(assets->id())); -} - -bool UpdateInterface::decompressArchive(const QString & archivePath, const QString & destDir) -{ - QString dest = destDir; - - if (!dest.endsWith("/")) - dest.append("/"); - - dest.append(QFileInfo(archivePath).completeBaseName()); - - MinizInterface mIface(progress, MinizInterface::PCM_SIZE, params->logLevel); - - if (!mIface.unzipArchiveToPath(archivePath, dest)) { - reportProgress(tr("Failed to decompress %1").arg(archivePath), QtCriticalMsg); - return false; - } - - return true; -} - -bool UpdateInterface::copyFlaggedAssets() -{ - assets->setFilterFlags(UPDFLG_CopyDest); - //reportProgress(tr("Asset filter applied: %1 Assets found: %2").arg(updateFlagsToString(UPDFLG_CopyDest)).arg(assets->count()), QtDebugMsg); - - for (int i = 0; i < assets->count(); ++i) { - assets->getSetId(i); - if (!copyAsset()) - return false; - } - - return true; -} - -bool UpdateInterface::copyAsset() -{ - if (assets->flags() & UPDFLG_CopyStructure) - return copyStructure(); - else if (assets->flags() & UPDFLG_CopyFiles) - return copyFiles(); - - return true; -} - -bool UpdateInterface::copyStructure() -{ - //reportProgress(tr("Copy directory structure"), QtDebugMsg); - - if (progress) { - progress->setValue(0); - progress->setMaximum(100); - } - - QDir srcPath(QString("%1/A%2/%3").arg(decompressDir).arg(assets->id()).arg(QFileInfo(assets->filename()).completeBaseName())); - - if (!srcPath.exists()) { - reportProgress(tr("Decompressed file structure not found at %1").arg(QDir::toNativeSeparators(srcPath.path())), QtCriticalMsg); - return false; - } - - reportProgress(tr("Copy file structure from: %1").arg(QDir::toNativeSeparators(srcPath.path())), QtDebugMsg); - - QString baseDir = updateDir; - - if (!assets->subDirectory().isEmpty()) - baseDir.append("/" % assets->subDirectory()); - - // create directory structure first as there is no order to QDirIterator if both files and directories selected - // also supports creation of empty directories - QDirIterator itdirs(srcPath.path(), QDir::AllDirs | QDir::NoDot | QDir::NoDotDot, QDirIterator::Subdirectories); - - int cnt = 0; - - while (itdirs.hasNext()) - { - QFileInfo src(itdirs.next()); - - QString subPath(src.absoluteFilePath()); - subPath.replace(srcPath.path(), ""); - - QString dir = baseDir % subPath; - - if (!QDir().mkpath(dir)) { - reportProgress(tr("Failed to create directory %1").arg(dir), QtCriticalMsg); - return false; - } - - reportProgress(tr("Check/create directory: %1").arg(dir), QtDebugMsg); - cnt++; - } - - reportProgress(tr("Directories checked/created: %1").arg(cnt), QtInfoMsg); - - QRegularExpression filter(assets->copyFilter(), QRegularExpression::CaseInsensitiveOption); - //reportProgress(tr("Copy filter pattern: %1").arg(filter.pattern()), QtDebugMsg); - - QDirIterator itcnt(srcPath.path(), QDir::Files, QDirIterator::Subdirectories); - - cnt = 0; - - //reportProgress(tr("Calculating number of files"), QtDebugMsg); - - while (itcnt.hasNext()) - { - QFileInfo src(itcnt.next()); - - if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter))) - continue; - - cnt++; - } - - if (progress) - progress->setMaximum(cnt); - - QDirIterator itfiles(srcPath.path(), QDir::Files, QDirIterator::Subdirectories); - - cnt = 0; - - while (itfiles.hasNext()) - { - QFileInfo src(itfiles.next()); - - if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter))) - continue; - - QString subPath(src.absoluteFilePath()); - subPath.replace(srcPath.path(), ""); - - QFile dest(baseDir % subPath); - - if (dest.exists()) { - if (!dest.remove()) { - reportProgress(tr("Failed to delete existing file %1").arg(dest.fileName()), QtCriticalMsg); - return false; - } - } - - if (!QFile::copy(src.absoluteFilePath(), dest.fileName())) { - reportProgress(tr("Failed to copy file %1").arg(src.absoluteFilePath()), QtCriticalMsg); - return false; - } - - reportProgress(tr("Copied %1 to %2").arg(src.fileName()).arg(dest.fileName()), QtDebugMsg); - cnt++; - if (progress) - progress->setValue(cnt); - } - - if (progress) - progress->setValue(progress->maximum()); - - reportProgress(tr("Files copied: %1").arg(cnt), QtInfoMsg); - - return true; -} - -bool UpdateInterface::copyFiles() -{ - //reportProgress(tr("Copy files"), QtDebugMsg); - - if (progress) { - progress->setValue(0); - progress->setMaximum(100); - } - - QString srcPath; - - if (assets->flags() & UPDFLG_Decompress) - srcPath = QString("%1/A%2/%3").arg(decompressDir).arg(assets->id()).arg(QFileInfo(assets->filename()).completeBaseName()); - else if (assets->flags() & UPDFLG_Download) - srcPath = QString("%1/A%2").arg(downloadDir).arg(assets->id()); - else { - reportProgress(tr("Unable to determine source directory for asset %1").arg(assets->filename()), QtCriticalMsg); - return false; - } - - reportProgress(tr("Copy files from %1").arg(srcPath), QtDebugMsg); - - QString destPath = updateDir; - - if (!assets->subDirectory().isEmpty()) - destPath.append("/" % assets->subDirectory()); - - if (!QDir().mkpath(destPath)) { - reportProgress(tr("Failed to create directory %1").arg(QDir::toNativeSeparators(destPath)), QtCriticalMsg); - return false; - } - - reportProgress(tr("Check/create directory: %1").arg(destPath), QtDebugMsg); - reportProgress(tr("Directories checked/created: %1").arg(1), QtDebugMsg); - - QRegularExpression filter(assets->copyFilter(), QRegularExpression::CaseInsensitiveOption); - //reportProgress(tr("Copy filter pattern: %1").arg(filter.pattern()), QtDebugMsg); - - QDirIterator itcnt(srcPath, QDir::Files, QDirIterator::Subdirectories); - - int cnt = 0; - - //reportProgress(tr("Calculating number of files"), QtDebugMsg); - - while (itcnt.hasNext()) - { - QFileInfo src(itcnt.next()); - - if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter))) - continue; - - cnt++; - } - - if (progress) - progress->setMaximum(cnt); - - QDirIterator itfiles(srcPath, QDir::Files, QDirIterator::Subdirectories); - - cnt = 0; - - while (itfiles.hasNext()) - { - QFileInfo src(itfiles.next()); - - if ((!filter.pattern().isEmpty()) && (!src.fileName().contains(filter))) - continue; - - QFile dest(destPath % "/" % src.fileName()); - - if (dest.exists()) { - if (!dest.remove()) { - reportProgress(tr("Failed to delete existing file %1").arg(dest.fileName()), QtCriticalMsg); - return false; - } - reportProgress(tr("Deleted file: %1").arg(dest.fileName()), QtDebugMsg); - } - - if (!QFile::copy(src.absoluteFilePath(), dest.fileName())) { - reportProgress(tr("Failed to copy file %1").arg(src.absoluteFilePath()), QtCriticalMsg); - return false; - } - - reportProgress(tr("Copied %1 to %2").arg(src.fileName()).arg(dest.fileName()), QtDebugMsg); - cnt++; - if (progress) - progress->setValue(cnt); - } - - if (!cnt) { - reportProgress(tr("No downloaded or decompressed files matched filter '%1'").arg(assets->copyFilter()), QtCriticalMsg); - return false; - } - - if (progress) - progress->setValue(progress->maximum()); - - reportProgress(tr("Files copied: %1").arg(cnt), QtInfoMsg); - - return true; -} - -bool UpdateInterface::preparation() -{ - progressMessage(tr("Preparing")); - int cnt = 0; - - if (progress) { - progress->setValue(cnt); - progress->setMaximum(5); - } - - if (!setRunFolders()) { - reportProgress(tr("Set run folders failed"), QtDebugMsg); - return false; - } - - if (progress) - progress->setValue(++cnt); - - if (!repoReleasesMetaData()) { - return false; - } - - if (progress) - progress->setValue(++cnt); - - if (!releases->getSetId(params->updateRelease)) { - reportProgress(tr("Set release id from update release '%1' failed").arg(params->updateRelease), QtCriticalMsg); - return false; - } - - if (progress) - progress->setValue(++cnt); - - if (!repoReleaseAssetsMetaData()) - return false; - - if (progress) - progress->setValue(++cnt); - - if (!flagAssets()) - return false; - - if (progress) - progress->setValue(++cnt); - - return true; -} - -bool UpdateInterface::flagAssets() -{ - progressMessage(tr("Flagging assets")); - - for (int i = 0; i < params->assets.size(); i++) { - const UpdateParameters::AssetParams & ap = params->assets.at(i); - if (!getSetAssets(ap)) - return false; - } - - return true; -} - -bool UpdateInterface::download() -{ - progressMessage(tr("Downloading assets")); - - if (!downloadFlaggedAssets()) { - reportProgress(tr("Unable to download flagged assets"), QtDebugMsg); - return false; - } - - return true; -} - -bool UpdateInterface::decompress() -{ - progressMessage(tr("Decompressing assets")); - - if (!decompressFlaggedAssets()) { - reportProgress(tr("Unable to decompress flagged assets"), QtDebugMsg); - return false; - } - - return true; -} - -bool UpdateInterface::copyToDestination() -{ - progressMessage(tr("Copying to destination")); - - if (!copyFlaggedAssets()) { - reportProgress(tr("Unable to copy assets"), QtDebugMsg); - return false; - } - - return true; -} - -bool UpdateInterface::housekeeping() -{ - progressMessage(tr("Housekeeping")); - int cnt = 0; - - if (progress) { - progress->setValue(cnt); - progress->setMaximum(2); - } - - if (progress) - progress->setValue(++cnt); - - g.lastUpdateDir(params->updateDir); // used by sdsync - - if (params->flags & UPDFLG_DelDownloads) { - reportProgress(tr("Delete download directory: %1").arg(downloadDir), QtDebugMsg); - QDir d(downloadDir); - if (!d.removeRecursively()) - reportProgress(tr("Failed to delete downloads folder %1").arg(downloadDir), QtCriticalMsg); - } - - if (progress) - progress->setValue(++cnt); - - if (params->flags & UPDFLG_DelDecompress) { - if (!params->decompressDirUseDwnld) { - reportProgress(tr("Delete decompress directory: %1").arg(decompressDir), QtDebugMsg); - QDir d(decompressDir); - if (!d.removeRecursively()) - reportProgress(tr("Failed to delete decompress folder %1").arg(decompressDir), QtCriticalMsg); - } - else if (!(params->flags & UPDFLG_DelDownloads)) { - assets->setFilterFlags(UPDFLG_Decompress); - for (int i = 0; i < assets->count(); i++) { - assets->getSetId(i); - QString path = QString("%1/A%2/%3").arg(decompressDir).arg(assets->id()).arg(QFileInfo(assets->filename()).completeBaseName()); - reportProgress(tr("Delete decompress folder: %1").arg(path), QtDebugMsg); - //QDir d(path); - if (!QDir(path).removeRecursively()) - reportProgress(tr("Failed to delete decompress folder %1").arg(path), QtCriticalMsg); - } - } - } - - if (progress) - progress->setValue(++cnt); - - return true; -} - -// as unique per component override if used -bool UpdateInterface::asyncInstall() -{ - return true; -} - -bool UpdateInterface::saveReleaseSettings() -{ - reportProgress(tr("Save release settings"), QtDebugMsg); - g.component[m_id].release(releases->name()); - g.component[m_id].version(releases->version()); - g.component[m_id].releaseId(releases->id()); - g.component[m_id].date(releases->date()); - - return true; -} - -/* - UpdateFactories -*/ - -UpdateFactories::UpdateFactories(QWidget * parent) : - QWidget(parent) -{ - registerUpdateFactories(); -} - -UpdateFactories::~UpdateFactories() -{ - unregisterUpdateFactories(); -} - -void UpdateFactories::registerUpdateFactory(UpdateFactoryInterface * factory) -{ - foreach (UpdateFactoryInterface * registeredFactory, registeredUpdateFactories) { - if (registeredFactory->id() == factory->id()) { - qDebug() << "Duplicate factory - id:" << factory->id() << "name:" << factory->name(); - return; - } - } - registeredUpdateFactories.append(factory); - qDebug() << "Registered update factory:" << factory->name(); -} - -void UpdateFactories::registerUpdateFactories() -{ - // Note: register in logical updating sequence - // SDCard should be first as it may clean up/erase commmon destination directory - registerUpdateFactory(new UpdateFactory(this)); - registerUpdateFactory(new UpdateFactory(this)); - registerUpdateFactory(new UpdateFactory(this)); - registerUpdateFactory(new UpdateFactory(this)); - registerUpdateFactory(new UpdateFactory(this)); - - // Note: Companion must be last as its install requires the app to be closed and thus would interrupt the update loop - registerUpdateFactory(new UpdateFactory(this)); -} - -void UpdateFactories::unregisterUpdateFactories() -{ - foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) - delete factory; -} - -UpdateInterface * UpdateFactories::getInstance(const int id) -{ - foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { - if (id == factory->id()) - return factory->instance(); - } - qDebug() << "Critical error - Interface not found for id:" << id; - return nullptr; -} - -const QString UpdateFactories::name(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->name(); - - return ""; -} - -void UpdateFactories::saveAssetSettings(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - iface->saveAssetSettings(); -} - -UpdateParameters * const UpdateFactories::getParams(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->getParams(); - - return nullptr; -} - -void UpdateFactories::resetEnvironment(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - iface->resetEnvironment(); -} - -void UpdateFactories::resetAllEnvironments() -{ - foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { - resetEnvironment(factory->id()); - } -} - -void UpdateFactories::setRunUpdate(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - iface->setRunUpdate(); -} - -const QMap UpdateFactories::sortedComponentsList(bool updateableOnly) -{ - QMap map; - - foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { - if (updateableOnly && !factory->instance()->isUpdateable()) - continue; - map.insert(factory->name(), factory->id()); - } - - return map; -} - -void UpdateFactories::clearRelease(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - iface->clearRelease(); -} - -void UpdateFactories::setReleaseChannel(const int id, int channel) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - iface->setReleaseChannel(channel); -} - -void UpdateFactories::setReleaseId(const int id, QString val) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - iface->setReleaseId(val); -} - -const QString UpdateFactories::currentRelease(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->currentRelease(); - - return ""; -} - -const QString UpdateFactories::updateRelease(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->updateRelease(); - - return ""; -} - -const bool UpdateFactories::isLatestRelease(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->isLatestRelease(); - - return true; -} - -const QString UpdateFactories::latestRelease(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->latestRelease(); - - return ""; -} - -const QStringList UpdateFactories::releases(const int id) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->getReleases(); - - return QStringList(); -} - -bool UpdateFactories::update(const int id, ProgressWidget * progress) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->update(progress); - - return false; -} - -bool UpdateFactories::updateAll(ProgressWidget * progress) -{ - bool ret = false; - - foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { - ret = factory->instance()->update(progress); - if (!ret) - break; - } - - return ret; -} - -const bool UpdateFactories::isUpdateAvailable(QMap & list) -{ - bool ret = false; - - list.clear(); - - foreach (UpdateFactoryInterface * factory, registeredUpdateFactories) { - if (factory->instance()->isUpdateable() && factory->instance()->isUpdateAvailable()) { - list.insert(factory->name(), factory->id()); - ret = true; - } - } - - return ret; -} - -bool UpdateFactories::getReleaseJsonAsset(const int id, const QString assetName, QJsonDocument * json) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->getReleaseJsonAsset(assetName, json); - - return false; -} - -bool UpdateFactories::getRepoJsonFile(const int id, const QString filename, QJsonDocument * json) -{ - UpdateInterface * iface = getInstance(id); - if (iface) - return iface->getRepoJsonFile(filename, json); - - return false; -} diff --git a/companion/src/updates/updateinterface.h b/companion/src/updates/updateinterface.h index 9afdde241..16b905b76 100644 --- a/companion/src/updates/updateinterface.h +++ b/companion/src/updates/updateinterface.h @@ -21,65 +21,15 @@ #pragma once -#include "repomodels.h" #include "constants.h" -#include "progresswidget.h" #include "appdata.h" +#include "updatestatus.h" +#include "updatenetwork.h" +#include "repo.h" +#include "updateparameters.h" #include -#include -#include - -class UpdateParameters : public QObject -{ - Q_OBJECT - - public: - enum UpdateFilterType { - UFT_None, - UFT_Exact, - UFT_Startswith, - UFT_Endswith, - UFT_Contains, - UFT_Expression, - }; - - static QStringList updateFilterTypeList(); - - static QString updateFilterTypeToString(UpdateFilterType uft); - - struct AssetParams { - int processes; - int flags; - UpdateFilterType filterType; - QString filter; - int maxExpected; - QString destSubDir; - UpdateFilterType copyFilterType; - QString copyFilter; - }; - - int flags; - int logLevel; - QString fwFlavour; - QString language; - int releaseChannel; - QString currentRelease; - QString updateRelease; - QString downloadDir; - bool decompressDirUseDwnld; - QString decompressDir; - QString sdDir; - bool updateDirUseSD; - QString updateDir; - QVector assets; - - UpdateParameters(QObject * parent); - virtual ~UpdateParameters() {} - - AssetParams & addAsset(); - QString buildFilterPattern(const UpdateFilterType filterType, const QString & filter); -}; +#include class UpdateInterface : public QWidget { @@ -87,13 +37,19 @@ class UpdateInterface : public QWidget public: - enum DownloadDataType { - DDT_Binary, - DDT_Content, - DDT_SaveToFile, - DDT_MetaData, + // The CID is used as a key to the application settings therefore + // ids must be explicit so no renumbering if components removed + // new components must be added to the end of the enum + enum ComponentIdentity { + CID_Unknown = -1, + CID_SDCard = 0, + CID_Firmware = 1, + CID_Sounds = 2, + CID_Themes = 3, + CID_MultiProtocol = 4, + CID_Companion = 5, }; - Q_ENUM(DownloadDataType) + Q_ENUM(ComponentIdentity) enum UpdateFlags { UPDFLG_None = 0, @@ -114,212 +70,81 @@ class UpdateInterface : public QWidget }; Q_ENUM(UpdateFlags) - // The CID is used as a key to the application settings therefore - // ids must be explicit so no renumbering if components removed - // new components must be added to the end of the enum - enum ComponentIdentity { - CID_Unknown = -1, - CID_SDCard = 0, - CID_Firmware = 1, - CID_Sounds = 2, - CID_Themes = 3, - CID_MultiProtocol = 4, - CID_Companion = 5, - }; - Q_ENUM(ComponentIdentity) - - explicit UpdateInterface(QWidget * parent); + explicit UpdateInterface(QWidget * parent, ComponentIdentity id, QString name); virtual ~UpdateInterface(); - const int id() const { return (int)m_id; } - const QString name() const { return m_name; } + virtual void assetSettingsSave(); + virtual const bool isReleaseLatest(); + virtual const bool isUpdateAvailable(); + virtual const bool isVersionLatest(const QString & current, const QString & latest); + 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 releaseClear(); + const QStringList releaseList(); + void resetEnvironment(); + void setReleaseChannel(const int channel); + void setRunUpdate(); protected: - friend class UpdateFactories; - - ReleasesMetaData *releases; - AssetsMetaData *assets; - UpdateParameters *params; - ProgressWidget *progress; - - QString downloadDir; - QString decompressDir; - QString updateDir; - - virtual bool update(ProgressWidget * progress = nullptr); - - virtual void initAssetSettings() = 0; - virtual void loadAssetSettings(); - virtual void saveAssetSettings(); - - virtual bool preparation(); - virtual bool flagAssets(); - virtual bool download(); - virtual bool decompress(); - virtual bool copyToDestination(); - virtual bool copyAsset(); - virtual bool housekeeping(); + virtual void assetSettingsInit() = 0; + virtual void assetSettingsLoad(); virtual bool asyncInstall(); + virtual bool copyAsset(); + virtual bool copyToDestination(); + virtual bool decompress(); + virtual bool download(); + virtual bool flagAssets(); + virtual bool housekeeping(); + virtual bool preparation(); - virtual const bool isUpdateAvailable(); - virtual const QString currentVersion(); - virtual const QString currentRelease(); - virtual const QString updateRelease(); - virtual const bool isLatestRelease(); - virtual const bool isLatestVersion(const QString & current, const QString & latest); - virtual QString latestRelease(); - - void clearRelease(); - const QStringList getReleases(); - - void init(ComponentIdentity id, QString name, QString repo, QString nightly = "", int resultsPerPage = -1); - - void setId(ComponentIdentity id) { m_id = id; } - void setName(QString name) { m_name = name; } - - void setReleaseChannel(int channel); - void setReleaseId(QString val); - - void setParamFolders(); - UpdateParameters * getParams() { return params; } - void resetEnvironment(); - void setRunUpdate() { params->flags |= UPDFLG_Update; } - bool isUpdateable(); - - bool repoReleasesMetaData(); - bool repoReleaseAssetsMetaData(); - - bool downloadReleasesMetaData(); - bool downloadReleaseLatestMetaData(); - bool downloadReleaseMetaData(const int releaseId); - - bool getReleaseJsonAsset(const QString assetName, QJsonDocument * json); - bool downloadReleaseAssetsMetaData(const int releaseId); - bool downloadAssetMetaData(const int assetId); - bool getSetAssets(const UpdateParameters::AssetParams & ap); - bool downloadAsset(int row); - bool downloadFlaggedAssets(); - bool decompressAsset(int row); - bool decompressFlaggedAssets(); + bool copyFiles(); bool copyFlaggedAssets(); bool copyStructure(); - bool copyFiles(); - - bool saveReleaseSettings(); - - bool downloadAssetToBuffer(const int assetId); - bool downloadTextFileToBuffer(const QString & path); - void downloadFileToBuffer(const QString & url); - bool convertDownloadToJson(QJsonDocument * json); - bool decompressArchive(const QString & archivePath, const QString & destPath); - QByteArray * getDownloadBuffer() { return buffer; } - - bool getRepoJsonFile(const QString filename, QJsonDocument * json); - - void reportProgress(const QString & text, const int type = QtInfoMsg); - void progressMessage(const QString & text); - void criticalMsg(const QString & msg); - static QString downloadDataTypeToString(DownloadDataType val); - static QString updateFlagsToString(UpdateFlags val); - void setFlavourLanguage(); - bool isValidSettingsIndex() { return m_id > -1 && m_id < MAX_COMPONENTS; } - - private slots: - void onDownloadFinished(QNetworkReply * reply, DownloadDataType ddt, int subtype); - - private: - QNetworkAccessManager manager; - QNetworkRequest request; - QNetworkReply *reply; - QByteArray *buffer; - QFile *file; - QUrl url; - int m_id; - QString m_name; - - bool downloadSuccess; + bool decompressAsset(int row); + const QString decompressDir() const; + bool decompressFlaggedAssets(); + const QString downloadDir() const; + bool downloadFlaggedAssets(); + void init(QString repo, QString nightly = "", int resultsPerPage = -1); + const bool isSettingsIndexValid() const; + UpdateNetwork* const network() const; + Repo* const repo() const; + bool retrieveAssetsJsonFile(const QString & assetName, QJsonDocument * json); + bool retrieveRepoJsonFile(const QString & filename, QJsonDocument * json); + bool setFilteredAssets(const UpdateParameters::AssetParams & ap); + void setFirmwareFlavour(); + void setLanguage(); + void setParamFolders(); + void setReleaseId(QString name); + UpdateStatus* const status() const; + const QString updateDir() const; static QStringList versionToStringList(QString version); + static const QString updateFlagsToString(const int flags); + static const QString updateFlagToString(const int flag); - void initAppSettings(); - bool setRunFolders(); + private: + const ComponentIdentity m_id; + const QString m_name; + UpdateParameters* const m_params; + UpdateStatus* const m_status; + UpdateNetwork* const m_network; + Repo* const m_repo; + QString m_downloadDir; + QString m_decompressDir; + QString m_updateDir; + + void appSettingsInit(); bool checkCreateDirectory(const QString & dirSetting, const UpdateFlags flag); - - void downloadMetaData(const MetaDataType mdt, const QString & url); - void download(const DownloadDataType type, const int subtype, const QString & urlStr, const char * header, const QString & filePath); - void downloadBinaryToFile(const QString & url, const QString & filename); - - void parseMetaData(int mdt); -}; - -class UpdateFactoryInterface -{ - public: - explicit UpdateFactoryInterface() {} - virtual ~UpdateFactoryInterface() {} - virtual UpdateInterface * instance() = 0; - virtual const QString name() = 0; - virtual const int id() = 0; -}; - -template -class UpdateFactory : public UpdateFactoryInterface -{ - public: - explicit UpdateFactory(QWidget * parent) : - UpdateFactoryInterface(), - m_instance(new T(parent)) {} - - virtual ~UpdateFactory() {} - - virtual UpdateInterface * instance() { return m_instance; } - virtual const QString name() { return m_instance->name(); } - virtual const int id() { return m_instance->id(); } - - private: - UpdateInterface *m_instance; -}; - -class UpdateFactories : public QWidget -{ - Q_OBJECT - - public: - explicit UpdateFactories(QWidget * parent = nullptr); - virtual ~UpdateFactories(); - - const QString name(const int id); - - void registerUpdateFactory(UpdateFactoryInterface * factory); - void registerUpdateFactories(); - void unregisterUpdateFactories(); - - void saveAssetSettings(const int id); - - UpdateParameters * const getParams(const int id); - void resetEnvironment(const int id); - void resetAllEnvironments(); - void setRunUpdate(const int id); - const QMap sortedComponentsList(bool updateableOnly = false); - - void clearRelease(const int id); - void setReleaseChannel(const int id, int channel); - void setReleaseId(const int id, QString val); - const QString currentRelease(const int id); - const QString updateRelease(const int id); - const bool isLatestRelease(const int id); - const QString latestRelease(const int id); - const QStringList releases(const int id); - bool getReleaseJsonAsset(const int id, const QString assetName, QJsonDocument * json); - - bool getRepoJsonFile(const int id, const QString filename, QJsonDocument * json); - - bool update(const int id, ProgressWidget * progress = nullptr); - bool updateAll(ProgressWidget * progress = nullptr); - const bool isUpdateAvailable(QMap & names); - - private: - QVector registeredUpdateFactories; - - UpdateInterface * getInstance(const int id); + bool decompressArchive(const QString & archivePath, const QString & destPath); + bool releaseSettingsSave(); + bool setRunFolders(); }; diff --git a/companion/src/updates/updatemultiprotocol.cpp b/companion/src/updates/updatemultiprotocol.cpp index b4dfb3d27..7d43dfca2 100644 --- a/companion/src/updates/updatemultiprotocol.cpp +++ b/companion/src/updates/updatemultiprotocol.cpp @@ -22,15 +22,15 @@ #include "updatemultiprotocol.h" UpdateMultiProtocol::UpdateMultiProtocol(QWidget * parent) : - UpdateInterface(parent) + UpdateInterface(parent, CID_MultiProtocol, tr("Multiprotocol")) { // GitHub REST API default ResultsPerPage = 30 - init(CID_MultiProtocol, tr("Multiprotocol"), QString(GITHUB_API_REPOS).append("/pascallanger/DIY-Multiprotocol-TX-Module"), "", 50); + init(QString(GH_API_REPOS).append("/pascallanger/DIY-Multiprotocol-TX-Module"), "", 50); } -void UpdateMultiProtocol::initAssetSettings() +void UpdateMultiProtocol::assetSettingsInit() { - if (!isValidSettingsIndex()) + if (!isSettingsIndexValid()) return; g.component[id()].initAllAssets(); @@ -49,7 +49,7 @@ void UpdateMultiProtocol::initAssetSettings() cad.desc("binaries"); cad.processes(UPDFLG_Common_Asset &~ UPDFLG_Decompress); cad.flags(cad.processes() | UPDFLG_CopyFiles); - cad.filterType(UpdateParameters::UFT_Expression); + cad.filterType(UpdateParameters::UFT_Pattern); cad.filter("^mm-stm-serial-.*\\.bin$"); cad.destSubDir("FIRMWARE"); } diff --git a/companion/src/updates/updatemultiprotocol.h b/companion/src/updates/updatemultiprotocol.h index 4e036b891..ae9874816 100644 --- a/companion/src/updates/updatemultiprotocol.h +++ b/companion/src/updates/updatemultiprotocol.h @@ -35,6 +35,5 @@ class UpdateMultiProtocol : public UpdateInterface virtual ~UpdateMultiProtocol() {} protected: - virtual void initAssetSettings() override; - + virtual void assetSettingsInit() override; }; diff --git a/companion/src/updates/updatenetwork.cpp b/companion/src/updates/updatenetwork.cpp new file mode 100644 index 000000000..028c7f5c5 --- /dev/null +++ b/companion/src/updates/updatenetwork.cpp @@ -0,0 +1,240 @@ +/* + * 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 "updatenetwork.h" +#include "constants.h" + +#include +#include + +constexpr char GH_API_VERSION[] {"2022-11-28"}; +constexpr char GH_ACCEPT_HEADER_CONTENT[] {"application/octet-stream"}; +constexpr char GH_ACCEPT_HEADER_METADATA[] {"application/vnd.github+json"}; +constexpr char GH_ACCEPT_HEADER_RAW[] {"application/vnd.github.raw"}; + +UpdateNetwork::UpdateNetwork(QObject * parent, UpdateStatus * status) : + QObject(parent), + m_status(status), + m_reply(nullptr), + m_buffer(new QByteArray()), + m_file(nullptr) +{ + QNetworkProxyFactory::setUseSystemConfiguration(true); +} + +UpdateNetwork::~UpdateNetwork() +{ + delete m_buffer; + if (m_reply) + delete m_reply; + if (m_file) + delete m_file; +} + +// 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"; + 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); + if (m_success) + convertBufferToJson(json); +} + +void UpdateNetwork::downloadToBuffer(const DownloadDataType type, const QString & url) +{ + m_success = false; + download(type, url, type == DDT_MetaData ? GH_ACCEPT_HEADER_METADATA : GH_ACCEPT_HEADER_CONTENT, QString()); +} + +void UpdateNetwork::downloadToFile(const QString & url, const QString & filePath) +{ + m_success = false; + + QFileInfo fi(filePath); + + m_status->progressMessage(tr("Downloading: %1").arg(fi.fileName())); + m_status->reportProgress(tr("Download: %1").arg(fi.fileName())); + + if (fi.exists()) { + m_status->reportProgress(tr("File exists: %1").arg(fi.fileName()), QtDebugMsg); + if (m_status) { + int reply = QMessageBox::question(m_status->progress(), CPN_STR_APP_NAME, tr("File %1 exists. Download again?").arg(fi.fileName()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No); + if (reply == QMessageBox::Cancel) { + return; + } + else if (reply == QMessageBox::No) { + m_success = true; + return; + } + else { + ; + } + } + } + else if (!QDir().mkpath(fi.path())) { + m_status->reportProgress(tr("Failed to create directory %1!").arg(fi.path()), QtCriticalMsg); + return; + } + + download(DDT_SaveToFile, url, GH_ACCEPT_HEADER_CONTENT, fi.absoluteFilePath()); +} + +void UpdateNetwork::download(const DownloadDataType type, const QString & urlStr, const char * header, const QString & filePath) +{ + m_status->setValue(0); + m_status->setMaximum(100); + + m_buffer->clear(); + m_success = false; + + if (type == DDT_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())); + return; + } + } + else + m_file = nullptr; + + m_url.setUrl(urlStr); + + if (!m_url.isValid()) { + m_status->criticalMsg(tr("Invalid URL: %1").arg(urlStr)); + return; + } + 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); + + m_request.setRawHeader(QByteArray("X-GitHub-Api-Version"), GH_API_VERSION); + m_request.setRawHeader(QByteArray("Accept"), QByteArray(header)); + + m_reply = m_manager.get(m_request); + + connect(m_reply, QOverload::of(&QNetworkReply::error), [=] (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)); + }); + + connect(m_reply, &QNetworkReply::sslErrors, [=]() { + m_status->reportProgress(tr("Ssl library version: %1").arg(QSslSocket::sslLibraryVersionString()), QtDebugMsg); + }); + + connect(m_reply, &QNetworkReply::readyRead, [=]() { + if (type == DDT_SaveToFile) { + m_file->write(m_reply->readAll()); + } + else { + const QByteArray qba = m_reply->readAll(); + m_buffer->append(qba); + } + }); + + connect(m_reply, &QNetworkReply::finished, [&]() { + 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; +} + +QByteArray * UpdateNetwork::getDownloadBuffer() +{ + return m_buffer; +} + +const bool UpdateNetwork::isSuccess() +{ + return m_success; +} + +void UpdateNetwork::onDownloadFinished(QNetworkReply * reply, DownloadDataType type) +{ + m_status->setValue(m_status->maximum()); + + if (type == DDT_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())); + + if (type == DDT_SaveToFile) + m_file->remove(); + } + else + m_success = true; + + if (type == DDT_SaveToFile) { + delete m_file; + m_file = nullptr; + } +} + +void UpdateNetwork::convertBufferToJson(QJsonDocument * json) +{ + m_success = false; + QJsonParseError res; + + *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())); + return; + } + + m_success = true; +} diff --git a/companion/src/updates/updatenetwork.h b/companion/src/updates/updatenetwork.h new file mode 100644 index 000000000..f89fa3859 --- /dev/null +++ b/companion/src/updates/updatenetwork.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include "updatestatus.h" + +#include +#include +#include +#include + +class UpdateNetwork : public QObject +{ + Q_OBJECT + + public: + + enum DownloadDataType { + DDT_Binary, + DDT_Content, + DDT_SaveToFile, + DDT_MetaData, + }; + Q_ENUM(DownloadDataType) + + explicit UpdateNetwork(QObject * parent, UpdateStatus * status); + virtual ~UpdateNetwork(); + + void convertBufferToJson(QJsonDocument * json); + void downloadMetaData(const QString & url, QJsonDocument * json); + void downloadJson(const QString & url, QJsonDocument * json); + void downloadToFile(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); + QByteArray * getDownloadBuffer(); + const bool isSuccess(); + + static QString downloadDataTypeToString(const DownloadDataType val); + + private slots: + void onDownloadFinished(QNetworkReply * reply, DownloadDataType ddt); + + private: + UpdateStatus *m_status; + QNetworkAccessManager m_manager; + QNetworkRequest m_request; + QNetworkReply *m_reply; + QByteArray *m_buffer; + QFile *m_file; + QUrl m_url; + bool m_success; + +}; diff --git a/companion/src/updates/updateoptionsdialog.cpp b/companion/src/updates/updateoptionsdialog.cpp index 010e1e884..aa1d5def4 100644 --- a/companion/src/updates/updateoptionsdialog.cpp +++ b/companion/src/updates/updateoptionsdialog.cpp @@ -30,29 +30,28 @@ #include #include -UpdateOptionsDialog::UpdateOptionsDialog(QWidget * parent, UpdateFactories * factories, const int idx, const bool isRun) : +UpdateOptionsDialog::UpdateOptionsDialog(QWidget * parent, UpdateInterface * iface, const int idx, const bool isRun) : QDialog(parent), ui(new Ui::UpdateOptionsDialog), - factories(factories), + iface(iface), + params(iface->params()), idx(idx), isRun(isRun) { ui->setupUi(this); if (!isRun) - factories->resetEnvironment(idx); + iface->resetEnvironment(); - params = factories->getParams(idx); + setWindowTitle(tr("%1 %2").arg(iface->name()).arg(tr("Options"))); - setWindowTitle(tr("%1 %2").arg(factories->name(idx)).arg(tr("Options"))); + ui->txtreleaseCurrent->setText(iface->releaseCurrent()); - ui->txtCurrentRelease->setText(factories->currentRelease(idx)); - - connect(ui->btnClearRelease, &QPushButton::clicked, [=]() { + connect(ui->btnReleaseClear, &QPushButton::clicked, [=]() { if (QMessageBox::question(this, CPN_STR_APP_NAME, tr("Clear current release information. Are you sure?"), QMessageBox::Yes |QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { - factories->clearRelease(idx); - ui->txtCurrentRelease->setText(factories->currentRelease(idx)); + iface->releaseClear(); + ui->txtreleaseCurrent->setText(iface->releaseCurrent()); emit changed(idx); } @@ -251,7 +250,7 @@ UpdateOptionsDialog::UpdateOptionsDialog(QWidget * parent, UpdateFactories * fac } if (!isRun) - factories->saveAssetSettings(idx); + iface->assetSettingsSave(); QDialog::accept(); }); diff --git a/companion/src/updates/updateoptionsdialog.h b/companion/src/updates/updateoptionsdialog.h index e3558c9ef..53426c10a 100644 --- a/companion/src/updates/updateoptionsdialog.h +++ b/companion/src/updates/updateoptionsdialog.h @@ -42,7 +42,7 @@ class UpdateOptionsDialog : public QDialog Q_OBJECT public: - UpdateOptionsDialog(QWidget * parent, UpdateFactories * factories, const int idx, const bool isRun); + UpdateOptionsDialog(QWidget * parent, UpdateInterface * iface, const int idx, const bool isRun); virtual ~UpdateOptionsDialog(); signals: @@ -53,10 +53,10 @@ class UpdateOptionsDialog : public QDialog private: Ui::UpdateOptionsDialog *ui; - UpdateFactories *factories; + UpdateInterface *iface; + UpdateParameters *params; const int idx; const bool isRun; - UpdateParameters *params; QVector cboAssetFilterTypes; QVector leAssetFilters; diff --git a/companion/src/updates/updateoptionsdialog.ui b/companion/src/updates/updateoptionsdialog.ui index df84fc506..437b307fc 100644 --- a/companion/src/updates/updateoptionsdialog.ui +++ b/companion/src/updates/updateoptionsdialog.ui @@ -23,14 +23,14 @@ - + Current Release: - + 0 @@ -43,7 +43,7 @@ - + 0 diff --git a/companion/src/updates/updateparameters.cpp b/companion/src/updates/updateparameters.cpp new file mode 100644 index 000000000..02fbeabae --- /dev/null +++ b/companion/src/updates/updateparameters.cpp @@ -0,0 +1,85 @@ +/* + * 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 "updateparameters.h" +#include "constants.h" + +UpdateParameters::UpdateParameters(QObject * parent) : + QObject(parent) +{ + logLevel = 0; + flags = 0; + decompressDirUseDwnld = true; + updateDirUseSD = true; +} + +UpdateParameters::AssetParams & UpdateParameters::addAsset() +{ + AssetParams ap; + ap.processes = 0; + ap.flags = 0; + ap.filterType = UFT_None; + ap.maxExpected = 0; + ap.copyFilterType = UFT_None; + assets.append(ap); + + return assets[assets.size() - 1]; +} + +QString UpdateParameters::buildFilterPattern(const UpdateFilterType filterType, const QString & filter) +{ + QString pattern(filter); + + pattern.replace("%FWFLAVOUR%", fwFlavour); + pattern.replace("%LANGUAGE%", language); + + switch ((int)filterType) { + case UFT_Exact: + pattern.prepend("^"); + pattern.append("$"); + break; + case UFT_Startswith: + pattern.prepend("^"); + break; + case UFT_Endswith: + pattern.append("$"); + break; + case UFT_Contains: + case UFT_Pattern: + break; + default: + pattern = ""; + } + + return pattern; +} + +// static +QStringList UpdateParameters::updateFilterTypeList() +{ + return { tr("None"), tr("Exact"), tr("Startswith"), tr("Endswith"), tr("Contains"), tr("Pattern") }; +} + +// static +QString UpdateParameters::updateFilterTypeToString(UpdateFilterType uft) +{ + return updateFilterTypeList().value(uft, CPN_STR_UNKNOWN_ITEM); +} diff --git a/companion/src/updates/updateparameters.h b/companion/src/updates/updateparameters.h new file mode 100644 index 000000000..90b792c34 --- /dev/null +++ b/companion/src/updates/updateparameters.h @@ -0,0 +1,75 @@ +/* + * 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 + +class UpdateParameters : public QObject +{ + Q_OBJECT + + public: + enum UpdateFilterType { + UFT_None, + UFT_Exact, + UFT_Startswith, + UFT_Endswith, + UFT_Contains, + UFT_Pattern, + }; + + static QStringList updateFilterTypeList(); + + static QString updateFilterTypeToString(UpdateFilterType uft); + + struct AssetParams { + int processes; + int flags; + UpdateFilterType filterType; + QString filter; + int maxExpected; + QString destSubDir; + UpdateFilterType copyFilterType; + QString copyFilter; + }; + + int flags; + int logLevel; + QString fwFlavour; + QString language; + int releaseChannel; + QString releaseCurrent; + QString releaseUpdate; + QString downloadDir; + bool decompressDirUseDwnld; + QString decompressDir; + QString sdDir; + bool updateDirUseSD; + QString updateDir; + QVector assets; + + UpdateParameters(QObject * parent); + virtual ~UpdateParameters() {} + + AssetParams & addAsset(); + QString buildFilterPattern(const UpdateFilterType filterType, const QString & filter); +}; diff --git a/companion/src/updates/updates.cpp b/companion/src/updates/updates.cpp index 6703633f5..31c2a0d7e 100644 --- a/companion/src/updates/updates.cpp +++ b/companion/src/updates/updates.cpp @@ -20,7 +20,7 @@ */ #include "updates.h" -#include "updateinterface.h" +#include "updatefactories.h" #include "updatesdialog.h" #include "progressdialog.h" #include "progresswidget.h" @@ -94,8 +94,9 @@ void Updates::autoUpdates(bool interactive) while (it.hasNext()) { it.next(); - factories->setRunUpdate(it.value()); - factories->updateRelease(it.value()); + UpdateInterface *iface = factories->instance(it.value()); + iface->setRunUpdate(); + iface->releaseUpdate(); } runUpdate(); diff --git a/companion/src/updates/updatesdcard.cpp b/companion/src/updates/updatesdcard.cpp index c07f0d374..9a10a4e4c 100644 --- a/companion/src/updates/updatesdcard.cpp +++ b/companion/src/updates/updatesdcard.cpp @@ -24,14 +24,14 @@ #include UpdateSDCard::UpdateSDCard(QWidget * parent) : - UpdateInterface(parent) + UpdateInterface(parent, CID_SDCard, tr("SD Card")) { - init(CID_SDCard, tr("SD Card"), QString(GH_REPOS_EDGETX).append("/edgetx-sdcard")); + init(QString(GH_API_REPOS_EDGETX).append("/edgetx-sdcard"), "Latest"); } -void UpdateSDCard::initAssetSettings() +void UpdateSDCard::assetSettingsInit() { - if (!isValidSettingsIndex()) + if (!isSettingsIndexValid()) return; g.component[id()].initAllAssets(); @@ -49,7 +49,22 @@ void UpdateSDCard::initAssetSettings() bool UpdateSDCard::flagAssets() { - progressMessage(tr("Flagging assets")); + status()->progressMessage(tr("Flagging assets")); + + QJsonDocument *json = new QJsonDocument(); + + const QString jsonFile("sdcard.json"); + + if (!retrieveAssetsJsonFile(jsonFile, json)) { + status()->reportProgress(tr("Unable to retrieve asset '%1' from release '%2'").arg(jsonFile).arg(repo()->releases()->name()), QtDebugMsg); + // assume older release where file not an asset + if (!retrieveRepoJsonFile(jsonFile, json)) { + status()->reportProgress(tr("Unable to retrieve file '%1' from repo '%2'").arg(jsonFile).arg(repo()->path()), QtCriticalMsg); + delete json; + return false; + } + } + /* { "targets": [ @@ -57,24 +72,14 @@ bool UpdateSDCard::flagAssets() ["FrSky Horus X10", "x10-", "c480x272"], */ - const QString mappingfile = "sdcard.json"; + const UpdateParameters::AssetParams & ap = params()->assets.at(0); - if (!downloadTextFileToBuffer(mappingfile)) { - return false; - } - - QJsonDocument *json = new QJsonDocument(); - - if (!convertDownloadToJson(json)) { - return false; - } - - const UpdateParameters::AssetParams & ap = params->assets.at(0); - - QRegularExpression filter(params->buildFilterPattern(ap.filterType, ap.filter), QRegularExpression::CaseInsensitiveOption); + QRegularExpression filter(params()->buildFilterPattern(ap.filterType, ap.filter), QRegularExpression::CaseInsensitiveOption); bool found = false; QString sdimage; + //qDebug() << *json; + if (json->isObject()) { const QJsonObject &obj = json->object(); @@ -100,7 +105,7 @@ bool UpdateSDCard::flagAssets() delete json; if (!found) { - reportProgress(tr("Radio flavour %1 not listed in %2").arg(params->fwFlavour).arg(mappingfile), QtCriticalMsg); + status()->reportProgress(tr("Radio flavour '%1' not listed in '%2'").arg(params()->fwFlavour).arg(jsonFile), QtCriticalMsg); return false; } @@ -108,7 +113,7 @@ bool UpdateSDCard::flagAssets() sd.filterType = UpdateParameters::UFT_Startswith; sd.filter = sdimage; - if (!getSetAssets(sd)) + if (!setFilteredAssets(sd)) return false; return true; diff --git a/companion/src/updates/updatesdcard.h b/companion/src/updates/updatesdcard.h index 926ef1f25..8ca81ec4a 100644 --- a/companion/src/updates/updatesdcard.h +++ b/companion/src/updates/updatesdcard.h @@ -33,7 +33,6 @@ class UpdateSDCard : public UpdateInterface virtual ~UpdateSDCard() {} protected: - virtual void initAssetSettings() override; - + virtual void assetSettingsInit() override; virtual bool flagAssets() override; }; diff --git a/companion/src/updates/updatesdialog.cpp b/companion/src/updates/updatesdialog.cpp index f3626e750..633c7bfca 100644 --- a/companion/src/updates/updatesdialog.cpp +++ b/companion/src/updates/updatesdialog.cpp @@ -139,7 +139,7 @@ UpdatesDialog::UpdatesDialog(QWidget * parent, UpdateFactories * factories) : QGridLayout *grid = new QGridLayout(); - QLabel *h1 = new QLabel(); + QCheckBox *h1 = new QCheckBox(); grid->addWidget(h1, row, col++); QLabel *h2 = new QLabel(tr("Name")); @@ -166,13 +166,15 @@ UpdatesDialog::UpdatesDialog(QWidget * parent, UpdateFactories * factories) : const int i = it.value(); const QString name = it.key(); + UpdateInterface *iface = factories->instance(i); + row++; col = 0; msg->setText(tr("Retrieving latest release information for %1").arg(name)); chkUpdate[i] = new QCheckBox(); chkUpdate[i]->setProperty("index", i); - chkUpdate[i]->setChecked(!factories->isLatestRelease(i)); + chkUpdate[i]->setChecked(!iface->isReleaseLatest()); grid->addWidget(chkUpdate[i], row, col++); grid->setAlignment(chkUpdate[i], Qt::AlignHCenter); @@ -184,26 +186,31 @@ UpdatesDialog::UpdatesDialog(QWidget * parent, UpdateFactories * factories) : cboRelChannel[i]->setCurrentIndex(g.component[i].releaseChannel()); grid->addWidget(cboRelChannel[i], row, col++); - lblCurrentRel[i] = new QLabel(factories->currentRelease(i)); + lblCurrentRel[i] = new QLabel(iface->releaseCurrent()); grid->addWidget(lblCurrentRel[i], row, col++); cboUpdateRel[i] = new QComboBox(); - cboUpdateRel[i]->addItems(factories->releases(i)); + cboUpdateRel[i]->addItems(iface->releaseList()); grid->addWidget(cboUpdateRel[i], row, col++); connect(cboRelChannel[i], QOverload::of(&QComboBox::currentIndexChanged), [=] (const int index) { - factories->setReleaseChannel(i, index); + iface->setReleaseChannel(index); + chkUpdate[i]->setChecked(!iface->isReleaseLatest()); cboUpdateRel[i]->clear(); - cboUpdateRel[i]->addItems(factories->releases(i)); + cboUpdateRel[i]->addItems(iface->releaseList()); + }); + + connect(cboUpdateRel[i], QOverload::of(&QComboBox::currentIndexChanged), [=] (const int index) { + chkUpdate[i]->setChecked(cboUpdateRel[i]->currentText() != lblCurrentRel[i]->text()); }); btnOptions[i] = new QPushButton(tr("Options")); connect(btnOptions[i], &QPushButton::clicked, [=]() { - UpdateOptionsDialog *dlg = new UpdateOptionsDialog(this, factories, i, true); + UpdateOptionsDialog *dlg = new UpdateOptionsDialog(this, iface, i, true); connect(dlg, &UpdateOptionsDialog::changed, [=](const int i) { - chkUpdate[i]->setChecked(!factories->isLatestRelease(i)); - lblCurrentRel[i]->setText(factories->currentRelease(i)); - cboUpdateRel[i]->setCurrentText(factories->updateRelease(i)); + chkUpdate[i]->setChecked(!iface->isReleaseLatest()); + lblCurrentRel[i]->setText(iface->releaseCurrent()); + cboUpdateRel[i]->setCurrentText(iface->releaseUpdate()); }); dlg->exec(); dlg->deleteLater(); @@ -214,6 +221,14 @@ UpdatesDialog::UpdatesDialog(QWidget * parent, UpdateFactories * factories) : ui->grpComponents->setLayout(grid); + connect(h1, &QCheckBox::clicked, [=] (bool checked) { + QMapIterator it(sortedCompList); + while (it.hasNext()) { + it.next(); + chkUpdate[it.value()]->setChecked(checked); + } + }); + QPushButton *btnSaveAsDefaults = new QPushButton(tr("Save as Defaults")); ui->buttonBox->addButton(btnSaveAsDefaults, QDialogButtonBox::ActionRole); connect(btnSaveAsDefaults, &QPushButton::clicked, [=]() { @@ -272,8 +287,8 @@ void UpdatesDialog::accept() if (chkUpdate[i]->isChecked()) { cnt++; - UpdateParameters *params = factories->getParams(i); - params->updateRelease = cboUpdateRel[i]->currentText(); + UpdateParameters *params = factories->instance(i)->params(); + params->releaseUpdate = cboUpdateRel[i]->currentText(); params->flags |= UpdateInterface::UPDFLG_Update; params->downloadDir = ui->leDownloadDir->text(); params->decompressDirUseDwnld = ui->chkDecompressDirUseDwnld->isChecked(); @@ -315,6 +330,6 @@ void UpdatesDialog::saveAsDefaults() it.next(); const int i = it.value(); g.component[i].releaseChannel((ComponentData::ReleaseChannel)cboRelChannel[i]->currentIndex()); - factories->saveAssetSettings(i); + factories->instance(i)->assetSettingsSave(); } } diff --git a/companion/src/updates/updatesdialog.h b/companion/src/updates/updatesdialog.h index 8feebc4b8..aa8ff9a11 100644 --- a/companion/src/updates/updatesdialog.h +++ b/companion/src/updates/updatesdialog.h @@ -21,7 +21,7 @@ #pragma once -#include "updateinterface.h" +#include "updatefactories.h" #include "appdata.h" #include @@ -44,8 +44,6 @@ class UpdatesDialog : public QDialog UpdatesDialog(QWidget * parent, UpdateFactories * factories); virtual ~UpdatesDialog(); - signals: - private slots: virtual void accept(); diff --git a/companion/src/updates/updatesounds.cpp b/companion/src/updates/updatesounds.cpp index e4d00f779..ee8f3f77c 100644 --- a/companion/src/updates/updatesounds.cpp +++ b/companion/src/updates/updatesounds.cpp @@ -27,9 +27,9 @@ #include UpdateSounds::UpdateSounds(QWidget * parent) : - UpdateInterface(parent) + UpdateInterface(parent, CID_Sounds, tr("Sounds")) { - init(CID_Sounds, tr("Sounds"), QString(GH_REPOS_EDGETX).append("/edgetx-sdcard-sounds")); + init(QString(GH_API_REPOS_EDGETX).append("/edgetx-sdcard-sounds")); langPacks = new QStandardItemModel(); } @@ -38,9 +38,9 @@ UpdateSounds::~UpdateSounds() delete langPacks; } -void UpdateSounds::initAssetSettings() +void UpdateSounds::assetSettingsInit() { - if (!isValidSettingsIndex()) + if (!isSettingsIndexValid()) return; g.component[id()].initAllAssets(); @@ -57,29 +57,20 @@ void UpdateSounds::initAssetSettings() bool UpdateSounds::flagAssets() { - progressMessage(tr("Processing available sounds")); - - QString pattern("sounds.json"); - assets->setFilterPattern(pattern); - - if (assets->count() < 0) { - reportProgress(tr("Asset not found in release '%1' using filter pattern '%2'").arg(releases->name()).arg(pattern), QtCriticalMsg); - return false; - } - else if (assets->count() > 1) { - reportProgress(tr("%1 assets found when %2 expected in release '%3' using filter pattern '%4'") - .arg(assets->count()).arg(1).arg(releases->name()).arg(pattern), QtCriticalMsg); - return false; - } - - if (!downloadAssetToBuffer(assets->getSetId(0))) { - return false; - } + status()->progressMessage(tr("Processing available sounds")); QJsonDocument *json = new QJsonDocument(); - if (!convertDownloadToJson(json)) { - return false; + const QString jsonFile("sounds.json"); + + if (!retrieveAssetsJsonFile(jsonFile, json)) { + status()->reportProgress(tr("Unable to retrieve asset '%1' from release '%2'").arg(jsonFile).arg(repo()->releases()->name()), QtDebugMsg); + // assume older release where file not an asset + if (!retrieveRepoJsonFile(jsonFile, json)) { + status()->reportProgress(tr("Unable to retrieve file '%1' from repo '%2'").arg(jsonFile).arg(repo()->path()), QtCriticalMsg); + delete json; + return false; + } } /* @@ -108,7 +99,7 @@ bool UpdateSounds::flagAssets() lang = langVariant.split("-").at(0); } - if (lang == params->language) { + if (lang == params()->language) { QStandardItem * item = new QStandardItem(); if (!obj.value("language").isUndefined()) @@ -129,7 +120,7 @@ bool UpdateSounds::flagAssets() delete json; if (langPacks->rowCount() < 1) { - reportProgress(tr("Language %1 not listed in %2").arg(params->language).arg(pattern), QtCriticalMsg); + status()->reportProgress(tr("Language '%1' not listed in '%2'").arg(params()->language).arg(jsonFile), QtCriticalMsg); return false; } @@ -141,7 +132,7 @@ bool UpdateSounds::flagAssets() QItemSelectionModel *selItems = dlg->selectedItems(); if (!ok || !selItems->hasSelection()) { - QMessageBox::warning(progress, CPN_STR_APP_NAME, tr("No language packs have been selected. Sounds update will be skipped!")); + QMessageBox::warning(status()->progress(), CPN_STR_APP_NAME, tr("No language packs have been selected. Sounds update will be skipped!")); dlg->deleteLater(); return true; } @@ -165,9 +156,9 @@ bool UpdateSounds::flagAssets() bool UpdateSounds::flagLanguageAsset(QString lang) { - progressMessage(tr("Flagging assets")); + status()->progressMessage(tr("Flagging assets")); - params->language = lang; - UpdateParameters::AssetParams ap = params->assets[0]; - return getSetAssets(ap); + params()->language = lang; + UpdateParameters::AssetParams ap = params()->assets[0]; + return setFilteredAssets(ap); } diff --git a/companion/src/updates/updatesounds.h b/companion/src/updates/updatesounds.h index b4ab4595c..946275fc0 100644 --- a/companion/src/updates/updatesounds.h +++ b/companion/src/updates/updatesounds.h @@ -35,9 +35,8 @@ class UpdateSounds : public UpdateInterface virtual ~UpdateSounds(); protected: - virtual void initAssetSettings() override; - virtual bool flagAssets() override; + virtual void assetSettingsInit() override; private: enum ItemModelDataRoles { diff --git a/companion/src/updates/updatestatus.cpp b/companion/src/updates/updatestatus.cpp new file mode 100644 index 000000000..db5fc54c7 --- /dev/null +++ b/companion/src/updates/updatestatus.cpp @@ -0,0 +1,64 @@ +/* + * 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 "updatestatus.h" + +#include + +UpdateStatus::UpdateStatus(QObject * parent) : + QObject(parent), + m_progress(nullptr), + m_logLevel(QtInfoMsg) +{ + +} + +void UpdateStatus::reportProgress(QString text, QtMsgType type) +{ + if (m_logLevel == QtDebugMsg || + (m_logLevel == QtInfoMsg && type > QtDebugMsg) || + (type < QtInfoMsg && type >= m_logLevel)) { + if (m_progress) { + m_progress->addMessage(text, type); + if (m_logLevel == QtDebugMsg) + qDebug() << text; + } + else + qDebug() << text; + } + + if (type == QtCriticalMsg || type == QtFatalMsg) + criticalMsg(text); +} + +void UpdateStatus::progressMessage(QString text) +{ + if (m_progress) + m_progress->setInfo(text); + else + qDebug() << text; +} + +void UpdateStatus::criticalMsg(QString msg) +{ + QMessageBox::critical(m_progress, tr("Update Interface"), msg); +} + diff --git a/companion/src/updates/updatestatus.h b/companion/src/updates/updatestatus.h new file mode 100644 index 000000000..202ad55a7 --- /dev/null +++ b/companion/src/updates/updatestatus.h @@ -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. + */ + +#pragma once + +#include "progresswidget.h" + +#include + +class UpdateStatus : public QObject +{ + Q_OBJECT + + public: + UpdateStatus(QObject * parent); + virtual ~UpdateStatus() {} + + void setInfo(QString text) { if (m_progress) m_progress->setInfo(text); } + void setLogLevel(int logLevel) { m_logLevel = logLevel; } + void setValue(int val) { if (m_progress) m_progress->setValue(val); } + + 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; } + ProgressWidget * progress() { return m_progress; } + + void reportProgress(QString text, QtMsgType type = QtInfoMsg); + void progressMessage(QString text); + void criticalMsg(QString msg); + + private: + ProgressWidget *m_progress; + int m_logLevel; +}; diff --git a/companion/src/updates/updatethemes.cpp b/companion/src/updates/updatethemes.cpp index c9777b56c..1c13f4937 100644 --- a/companion/src/updates/updatethemes.cpp +++ b/companion/src/updates/updatethemes.cpp @@ -22,14 +22,14 @@ #include "updatethemes.h" UpdateThemes::UpdateThemes(QWidget * parent) : - UpdateInterface(parent) + UpdateInterface(parent, CID_Themes, tr("Themes")) { - init(CID_Themes, tr("Themes"), QString(GH_REPOS_EDGETX).append("/themes")); + init(QString(GH_API_REPOS_EDGETX).append("/themes")); } -void UpdateThemes::initAssetSettings() +void UpdateThemes::assetSettingsInit() { - if (!isValidSettingsIndex()) + if (!isSettingsIndexValid()) return; g.component[id()].initAllAssets(); diff --git a/companion/src/updates/updatethemes.h b/companion/src/updates/updatethemes.h index 329a6e8b1..8af012c1e 100644 --- a/companion/src/updates/updatethemes.h +++ b/companion/src/updates/updatethemes.h @@ -33,6 +33,5 @@ class UpdateThemes : public UpdateInterface virtual ~UpdateThemes() {} protected: - virtual void initAssetSettings() override; - + virtual void assetSettingsInit() override; };