feat(cpn): add detailed progress to read and write radio (#7450)

This commit is contained in:
Neil Horne
2026-06-30 11:42:21 +10:00
committed by GitHub
parent eae6aa1c69
commit d34582686b
13 changed files with 410 additions and 201 deletions
+24 -2
View File
@@ -569,26 +569,43 @@ bool readSettings(const QString &filename, ProgressWidget *progress)
bool readSettingsSDCard(const QString &filename, ProgressWidget *progress,
bool fromRadio)
{
if (progress) {
progress->setValue(0);
progress->setMaximum(100);
progress->updateInfoAndMessages(TR("Initialising..."));
}
QString radioPath;
if (fromRadio) {
radioPath = findMassStoragePath("RADIO", true, progress);
qDebug() << "Searching for SD card, found" << radioPath;
} else {
radioPath = g.currentProfile().sdPath();
if (!QFile::exists(radioPath % "/RADIO"))
radioPath.clear();
}
if (radioPath.isEmpty()) {
QMessageBox::critical(progress, CPN_STR_TTL_ERROR, TR("Unable to find SD card!"));
const QString msg(TR("Unable to find SD card!"));
if (progress)
progress->addMessage(msg, QtFatalMsg);
else
QMessageBox::critical(nullptr, CPN_STR_TTL_ERROR, msg);
return false;
}
if (progress)
progress->setInfo(TR("Loading models and settings"));
RadioData radioData;
Storage inputStorage(radioPath);
inputStorage.setProgress(progress);
if (!inputStorage.load(radioData)) {
QString errorMsg = inputStorage.error();
if (errorMsg.isEmpty())
errorMsg = TR("Failed to read Models and Settings from")
% QDir::toNativeSeparators(radioPath);
@@ -597,10 +614,15 @@ bool readSettingsSDCard(const QString &filename, ProgressWidget *progress,
return false;
}
if (progress)
progress->addMessage(TR("Writing models and settings to temporary file..."));
Storage outputStorage(filename);
outputStorage.setProgress(progress);
if (!outputStorage.write(radioData)) {
QMessageBox::critical(progress, CPN_STR_TTL_ERROR,
TR("Failed to write Models and Setting file") % " "
TR("Failed to write Models and Setting to") % " "
% QDir::toNativeSeparators(filename));
return false;
}
+28 -55
View File
@@ -525,23 +525,13 @@ void MainWindow::customizeSplash()
void MainWindow::writeSettings()
{
StatusDialog *status = new StatusDialog(this, tr("Writing models and settings to radio"), tr("In progress..."), 400);
if (activeMdiChild())
activeMdiChild()->writeSettings(status);
delete status;
activeMdiChild()->writeModelsSettings();
}
void MainWindow::readSettings()
{
Board::Type board = getCurrentBoard();
QString tempFile;
if (Boards::getCapability(board, Board::HasSDCard))
tempFile = generateProcessUniqueTempFileName("temp.etx");
else
tempFile = generateProcessUniqueTempFileName("temp.bin");
QString tempFile(generateProcessUniqueTempFileName("temp.etx"));
qDebug() << "Reading models and settings into temp file: " << tempFile;
if (readSettingsFromRadio(tempFile)) {
@@ -568,16 +558,21 @@ bool MainWindow::readFirmwareFromRadio(const QString & filename)
bool MainWindow::readSettingsFromRadio(const QString & filename)
{
ProgressDialog progressDialog(this, tr("Read Models and Settings from Radio"), CompanionIcon("read_eeprom.png"));
bool result = ::readSettings(filename, progressDialog.progress());
if (!result) {
if (!progressDialog.isEmpty()) {
progressDialog.exec();
}
}
else {
statusBar()->showMessage(tr("Models and Settings read"), 2000);
}
ProgressDialog dlg(this, tr("Read Models and Settings from Radio"), CompanionIcon("read_eeprom.png"));
ProgressWidget * progress = dlg.progress();
progress->lock(false);
dlg.setProcessStarted();
progress->setInfo(tr("Initialising"));
bool result = ::readSettings(filename, progress);
dlg.setProcessStopped();
progress->setValue(progress->maximum());
progress->updateInfoAndMessages(tr("Finished %1").arg(result ? tr("successfully") : tr("with errors")));
progress->refresh();
QApplication::processEvents();
dlg.exec();
return result;
}
@@ -1488,52 +1483,30 @@ void MainWindow::chooseProfile()
void MainWindow::readSettingsSDPath()
{
ProgressDialog progressDialog(this, tr("Read Models and Settings from Profile SD Path"), CompanionIcon("read_eeprom.png"));
QString tempFile;
tempFile = generateProcessUniqueTempFileName("temp.etx");
qDebug() << "Reading models and settings from SD path into temp file: " << tempFile;
if (readSettingsFromSDPath(tempFile)) {
bool result = ::readSettingsSDCard(tempFile, progressDialog.progress(), false);
if (!result) {
if (!progressDialog.isEmpty())
progressDialog.exec();
} else {
statusBar()->showMessage(tr("Models and Settings read"), 2000);
MdiChild * child = createMdiChild();
child->newFile();
child->loadFile(tempFile, false);
child->show();
qunlink(tempFile);
}
}
bool MainWindow::readSettingsFromSDPath(const QString & filename)
{
ProgressDialog progressDialog(this, tr("Read Models and Settings from SD path"), CompanionIcon("read_eeprom.png"));
bool result = ::readSettingsSDCard(filename, progressDialog.progress(), false);
if (!result) {
if (!progressDialog.isEmpty()) {
progressDialog.exec();
}
}
else {
statusBar()->showMessage(tr("Models and Settings read"), 2000);
}
return result;
qunlink(tempFile);
}
void MainWindow::writeSettingsSDPath()
{
if (activeMdiChild()) {
int cnt = activeMdiChild()->invalidModels();
if (cnt) {
QMessageBox::critical(this, tr("Write Models and Settings to SD Path"),
tr("Operation aborted: %1 models have errors that may affect operation.\n%2")
.arg(cnt)
.arg(activeMdiChild()->modelErrorsList().join("\n")));
return;
}
StatusDialog *status = new StatusDialog(this, tr("Writing models and settings to SD path"), tr("In progress..."), 400);
activeMdiChild()->writeSettings(status, false);
delete status;
}
if (activeMdiChild())
activeMdiChild()->writeModelsSettings(false);
}
bool MainWindow::isSDPathValid()
-1
View File
@@ -154,7 +154,6 @@ class MainWindow : public QMainWindow
bool readSettingsFromRadio(const QString & filename);
bool readFirmwareFromRadio(const QString & filename);
bool readSettingsFromSDPath(const QString & filename);
bool checkProfileRadioExists(int profId);
+53 -18
View File
@@ -34,6 +34,8 @@
#include "filtereditemmodels.h"
#include "labels.h"
#include "firmwares/edgetx/edgetxinterface.h"
#include "progress/progressdialog.h"
#include "progress/progresswidget.h"
#include <algorithm>
#include <ExportableTableView>
@@ -1424,11 +1426,19 @@ bool MdiChild::saveAs(bool isNew)
return saveFile(fileName, true);
}
bool MdiChild::saveFile(const QString & filename, bool setCurrent)
bool MdiChild::saveFile(const QString & filename, bool setCurrent, bool toRadio)
{
radioData.fixModelFilenames();
Storage storage(filename);
bool result = storage.write(radioData);
bool result = false;
if (toRadio)
result = saveFileProgress(filename);
else {
Storage storage(filename);
result = storage.write(radioData);
}
if (!result) {
return false;
}
@@ -1451,6 +1461,29 @@ bool MdiChild::saveFile(const QString & filename, bool setCurrent)
return true;
}
bool MdiChild::saveFileProgress(const QString & filename)
{
ProgressDialog dlg(parentWidget(), tr("Write Models and Settings"),
CompanionIcon("write_eeprom.png"));
ProgressWidget * progress = dlg.progress();
progress->lock(false);
dlg.setProcessStarted();
progress->updateInfoAndMessages(tr("Initialising"));
Storage storage(filename);
storage.setProgress(progress);
bool result = storage.write(radioData);
dlg.setProcessStopped();
progress->updateInfoAndMessages(tr("Finished %1").arg(result ? tr("successfully") : tr("with errors")));
progress->setValue(progress->maximum());
progress->refresh();
QApplication::processEvents();
dlg.exec();
return result;
}
void MdiChild::closeFile(bool force)
{
forceCloseFlag = force;
@@ -1573,7 +1606,7 @@ int MdiChild::askQuestion(const QString & msg, QMessageBox::StandardButtons butt
return QMessageBox::question(this, CPN_STR_APP_NAME, msg, buttons, defaultButton);
}
void MdiChild::writeSettings(StatusDialog * status, bool toRadio)
void MdiChild::writeModelsSettings(bool toRadio)
{
// safeguard as the menu actions are enabled
int cnt = radioData.invalidModels();
@@ -1605,25 +1638,27 @@ void MdiChild::writeSettings(StatusDialog * status, bool toRadio)
if (toRadio) {
radioPath = findMassStoragePath("RADIO", true);
qDebug() << "Searching for SD card, found" << radioPath;
if (radioPath.isEmpty()) {
QMessageBox::critical(this, CPN_STR_TTL_ERROR, tr("Unable to find radio SD card!"));
return;
}
qDebug() << "SD card found" << radioPath;
} else {
radioPath = g.currentProfile().sdPath();
if (!QFile(radioPath % "/RADIO").exists())
radioPath.clear();
if (!QFile(radioPath % "/RADIO").exists()) {
QMessageBox::critical(this, CPN_STR_TTL_ERROR, tr("Unable to find SD path '%1'!")
.arg(QDir::toNativeSeparators(radioPath % "/RADIO")));
return;
}
}
if (radioPath.isEmpty()) {
qDebug() << "Radio SD card not found";
QMessageBox::critical(this, CPN_STR_TTL_ERROR, tr("Unable to find SD card!"));
return;
}
bool ret = saveFile(radioPath, false, toRadio);
if (saveFile(radioPath, false)) {
status->hide();
QMessageBox::information(this, CPN_STR_TTL_INFO, tr("Models and settings written"));
} else {
status->hide();
QMessageBox::critical(this, CPN_STR_TTL_ERROR, tr("Error writing models and settings!"));
if (!toRadio) {
if (ret)
QMessageBox::information(this, CPN_STR_TTL_INFO, tr("Models and settings written"));
else
QMessageBox::critical(this, CPN_STR_TTL_ERROR, tr("Error writing models and settings!"));
}
}
+3 -2
View File
@@ -114,9 +114,10 @@ class MdiChild : public QWidget
bool loadFile(const QString & fileName, bool resetCurrentFile=true);
bool save();
bool saveAs(bool isNew=false);
bool saveFile(const QString & fileName, bool setCurrent=true);
bool saveFile(const QString & fileName, bool setCurrent = true, bool toRadio = false);
bool saveFileProgress(const QString & fileName);
void closeFile(bool force = false);
void writeSettings(StatusDialog * status, bool toRadio = true);
void writeModelsSettings(bool toRadio = true);
void print(int model=-1, const QString & filename="");
void onFirmwareChanged();
+2 -1
View File
@@ -820,5 +820,6 @@ void SetupPanel::loadImagePreview()
else
ui->imagePreview->clear();
}
} else
ui->imagePreview->clear();
}
+20 -1
View File
@@ -33,7 +33,8 @@ ProgressWidget::ProgressWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ProgressWidget),
m_forceOpen(false),
m_hasDetails(false)
m_hasDetails(false),
m_logLevel(QtInfoMsg)
{
ui->setupUi(this);
ui->info->hide();
@@ -52,6 +53,7 @@ ProgressWidget::ProgressWidget(QWidget *parent) :
newFont.setPointSize(9);
#endif
ui->textEdit->setFont(newFont);
QTimer::singleShot(0, [this]() { this->refresh(); });
}
ProgressWidget::~ProgressWidget()
@@ -67,6 +69,7 @@ void ProgressWidget::stop()
void ProgressWidget::clearDetails() const
{
ui->textEdit->clear();
QCoreApplication::processEvents();
}
void ProgressWidget::forceOpen()
@@ -74,17 +77,20 @@ void ProgressWidget::forceOpen()
m_forceOpen = true;
ui->checkBox->hide();
toggleDetails();
QCoreApplication::processEvents();
}
void ProgressWidget::setInfo(const QString &text)
{
ui->info->show();
ui->info->setText(text);
QCoreApplication::processEvents();
}
void ProgressWidget::setMaximum(int value)
{
ui->progressBar->setMaximum(value);
QCoreApplication::processEvents();
}
int ProgressWidget::maximum()
@@ -95,6 +101,7 @@ int ProgressWidget::maximum()
void ProgressWidget::setValue(int value)
{
ui->progressBar->setValue(value);
QCoreApplication::processEvents();
}
void ProgressWidget::addText(const QString &text, const bool richText, const bool updateLast)
@@ -143,6 +150,15 @@ void ProgressWidget::addHtml(const QString & text, const bool updateLast)
void ProgressWidget::addMessage(const QString & text, const int & type, const bool richText, const bool updateLast)
{
// The allowed predicate only lets QtInfoMsg through when m_logLevel is QtDebugMsg or QtInfoMsg;
// when m_logLevel is QtWarningMsg, QtCriticalMsg, or QtFatalMsg, QtInfoMsg is always filtered out
const bool allowed = (m_logLevel == QtDebugMsg) ||
(m_logLevel == QtInfoMsg && type > QtDebugMsg) ||
(type < QtInfoMsg && type >= m_logLevel);
if (!allowed)
return;
QString color;
switch (type) {
case QtDebugMsg:
@@ -171,6 +187,8 @@ void ProgressWidget::addMessage(const QString & text, const int & type, const bo
else {
addHtml(QString("<font color=%1>").arg(color) % text % "</font><br>", updateLast);
}
QCoreApplication::processEvents();
}
QString ProgressWidget::getText() const
@@ -233,6 +251,7 @@ void ProgressWidget::refresh()
ui->info->update();
ui->progressBar->update();
ui->textEdit->update();
QCoreApplication::processEvents();
}
void ProgressWidget::updateInfoAndMessages(const QString & text, const int & type, const bool richText)
+2
View File
@@ -57,6 +57,7 @@ class ProgressWidget : public QWidget
void refresh();
void updateInfoAndMessages(const QString & text, const int & type = QtInfoMsg, const bool richText = false);
void updateLastMessage(const QString & text, const int & type = QtInfoMsg, const bool richText = false);
void setLoggingLevel(const int & level = QtInfoMsg) { m_logLevel = level; }
signals:
void detailsToggled();
@@ -72,4 +73,5 @@ class ProgressWidget : public QWidget
Ui::ProgressWidget *ui;
bool m_forceOpen;
bool m_hasDetails;
int m_logLevel;
};
+25 -20
View File
@@ -31,18 +31,17 @@ bool EtxFormat::load(RadioData & radioData)
QFile file(filename);
if (!file.open(QFile::ReadOnly)) {
setError(tr("Error opening file %1:\n%2.").arg(filename).arg(file.errorString()));
fatalMsg(tr("Error opening file %1:\n%2.").arg(filename).arg(file.errorString()));
return false;
}
QByteArray archiveContents = file.readAll();
qDebug() << "File" << filename << "read, size:" << archiveContents.size();
// qDebug() << "File" << filename << "read, size:" << archiveContents.size();
// open zip file
memset(&zip_archive, 0, sizeof(zip_archive));
if (!mz_zip_reader_init_mem(&zip_archive, archiveContents.data(), archiveContents.size(), 0)) {
qDebug() << tr("Error opening EdgeTX archive %1").arg(filename);
fatalMsg(tr("Error opening EdgeTX archive %1").arg(filename));
return false;
}
@@ -53,37 +52,41 @@ bool EtxFormat::load(RadioData & radioData)
bool EtxFormat::write(RadioData & radioData)
{
qDebug() << "Saving to archive" << filename;
// qDebug() << "Saving to archive" << filename;
memset(&zip_archive, 0, sizeof(zip_archive));
if (!mz_zip_writer_init_heap(&zip_archive, 0, MZ_ALLOCATION_SIZE)) {
setError(tr("Error initializing EdgeTX archive writer"));
fatalMsg(tr("Error initializing EdgeTX archive writer"));
return false;
}
bool result = LabelsStorageFormat::write(radioData);
if (result) {
// finalize archive and get contents
char * archiveContents;
size_t archiveSize;
if (mz_zip_writer_finalize_heap_archive(&zip_archive, (void **)&archiveContents, &archiveSize)) {
qDebug() << "Archive size" << archiveSize;
// qDebug() << "Archive size" << archiveSize;
// write contents to file
QFile file(filename);
if (file.open(QIODevice::WriteOnly)) {
qint64 len = file.write(archiveContents, archiveSize);
if (len != (qint64)archiveSize) {
setError(tr("Error writing file %1:\n%2.").arg(filename).arg(file.errorString()));
fatalMsg(tr("Error writing file %1:\n%2.").arg(filename).arg(file.errorString()));
result = false;
}
}
else {
setError(tr("Error creating EdgeTX file %1:\n%2.").arg(filename).arg(file.errorString()));
fatalMsg(tr("Error creating EdgeTX file %1:\n%2.").arg(filename).arg(file.errorString()));
result = false;
}
}
else {
setError(tr("Error creating EdgeTX archive"));
fatalMsg(tr("Error creating EdgeTX archive"));
result = false;
}
}
@@ -99,7 +102,7 @@ bool EtxFormat::loadFile(QByteArray & filedata, const QString & filename, bool o
if (!data) return optional;
qDebug() << QString("Extracted file %1, size=%2").arg(filename).arg(size);
//qDebug() << QString("Extracted file %1, size=%2").arg(filename).arg(size);
filedata.clear();
filedata.append((char *)data, size);
mz_free(data);
@@ -122,28 +125,28 @@ bool EtxFormat::loadImageFile(const QString & filename, bool optional)
if (imgfile.open(QIODevice::WriteOnly)) {
if (imgfile.write(ba) != (qint64)size) {
qDebug() << "Image data written does not match expected";
fatalMsg(tr("Image data written does not match expected"));
return false;
}
imgfile.close();
} else {
setError(tr("Unable to write %1 to cache").arg(filename));
fatalMsg(tr("Unable to write %1 to cache").arg(filename));
return false;
}
qDebug() << QString("Extracted file %1, size=%2").arg(srcpath).arg(size);
statusMsg(tr("Extracted image: %1").arg(srcpath));
return true;
}
bool EtxFormat::writeFile(const QByteArray & filedata, const QString & filename)
{
if (!mz_zip_writer_add_mem(&zip_archive, filename.toStdString().c_str(), filedata.data(), filedata.size(), MZ_DEFAULT_LEVEL)) {
setError(tr("Error adding %1 to archive").arg(filename));
fatalMsg(tr("Error adding %1 to archive").arg(filename));
return false;
}
qDebug() << "File" << filename << "written, size:" << filedata.size();
//qDebug() << "File" << filename << "written, size:" << filedata.size();
return true;
}
@@ -159,21 +162,22 @@ bool EtxFormat::writeImageFile(const QString & filename)
ba.append(imgfile.readAll());
imgfile.close();
} else {
setError(tr("Unable to open image %1").arg(filename));
fatalMsg(tr("Unable to open image %1").arg(filename));
return false;
}
QString destpath("IMAGES/" % filename);
if (!mz_zip_writer_add_mem(&zip_archive, destpath.toStdString().c_str(), ba.data(), ba.size(), MZ_DEFAULT_LEVEL)) {
setError(tr("Error adding file %1").arg(destpath));
fatalMsg(tr("Error adding file: %1").arg(destpath));
return false;
}
qDebug() << "File" << destpath << "written, size:" << ba.size();
statusMsg(tr("File written: %1").arg(filename));
return true;
}
fatalMsg(tr("No application image cache"));
return false;
}
@@ -183,6 +187,7 @@ bool EtxFormat::getFileList(std::list<std::string>& filelist)
if (count == 0) return false;
mz_zip_archive_file_stat file_stat;
for (int i=0; i<count; i++) {
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue;
if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) continue;
+154 -76
View File
@@ -22,6 +22,7 @@
#include "labeled.h"
#include "firmwares/opentx/opentxinterface.h"
#include "firmwares/edgetx/edgetxinterface.h"
#include "progressdialog.h"
#include <regex>
@@ -35,7 +36,14 @@ StorageType LabelsStorageFormat::probeFormat()
bool LabelsStorageFormat::load(RadioData & radioData)
{
int steps = 2; // initialisation and load radio.yml
bool hasLabels = getCurrentFirmware()->getCapability(HasModelLabels);
if (hasLabels)
steps++;
StorageType st = getStorageType(filename);
if (st == STORAGE_TYPE_UNKNOWN)
st = probeFormat();
@@ -49,43 +57,18 @@ bool LabelsStorageFormat::load(RadioData & radioData)
qDebug() << tr("Cannot find %1/MODELS/models.yml").arg(filename);
else
qDebug() << tr("Found %1/MODELS/models.yml").arg(filename);
}
else {
} else {
if (!QFile(filename).exists())
qDebug() << tr("Cannot find %1").arg(filename);
else
qDebug() << tr("Found %1").arg(filename);
}
if (!loadRadioSettings(radioData.generalSettings))
return false;
board = (Board::Type)radioData.generalSettings.variant;
// get models
// Scan for all models
EtxModelfiles modelFiles;
// Labels - Read the labels from labels.yml file
// - Always scan for /MODELS/modelXX.yml
// - Update possible labels with all found in models too
statusMsg(tr("Scanning for models..."));
QByteArray labelslistBuffer;
int sortOrder = 0;
if (loadFile(labelslistBuffer, "MODELS/labels.yml")) {
try {
if (!loadLabelsListFromYaml(radioData.labels, sortOrder, modelFiles, labelslistBuffer)) {
setError(tr("Can't load MODELS/labels.yml"));
}
} catch(const std::runtime_error& e) {
setError(tr("Can't load MODELS/labels.yml") + ":\n" + QString(e.what()));
}
}
// Save Sort Order
radioData.sortOrder = sortOrder;
// Scan for all models
std::list<std::string> filelist;
if (!getFileList(filelist))
return false;
@@ -103,8 +86,39 @@ bool LabelsStorageFormat::load(RadioData & radioData)
}
}
progressSetMaximum(steps + modelFiles.size());
steps = 0;
progressSetValue(++steps);
if (!loadRadioSettings(radioData.generalSettings))
return false;
board = (Board::Type)radioData.generalSettings.variant;
// Labels - Read the labels from labels.yml file
// - Always scan for /MODELS/modelXX.yml
// - Update possible labels with all found in models too
progressSetValue(++steps);
QByteArray labelslistBuffer;
int sortOrder = 0;
if (hasLabels && loadFile(labelslistBuffer, "MODELS/labels.yml", true)) {
try {
if (!loadLabelsListFromYaml(radioData.labels, sortOrder, modelFiles, labelslistBuffer)) {
fatalMsg(tr("Can't load MODELS/labels.yml"));
}
} catch(const std::runtime_error& e) {
fatalMsg(tr("Can't load MODELS/labels.yml") + ":\n" + QString(e.what()));
}
progressSetValue(++steps);
}
// Save Sort Order
radioData.sortOrder = sortOrder;
int modelIdx = 0;
bool hasLabels = getCurrentFirmware()->getCapability(HasModelLabels);
if (hasLabels)
radioData.models.resize(modelFiles.size());
@@ -118,12 +132,13 @@ bool LabelsStorageFormat::load(RadioData & radioData)
if (mc.modelIdx >= 0 && mc.modelIdx < (int)radioData.models.size()) {
modelIdx = mc.modelIdx;
if (!radioData.models[modelIdx].isEmpty()) {
qDebug() << QString("Warning: file %1 skipped as slot %2 already used").arg(mc.filename.c_str()).arg(mc.modelIdx + 1);
statusMsg(tr("Warning: file %1 skipped as slot %2 already used")
.arg(mc.filename.c_str()).arg(mc.modelIdx + 1), QtWarningMsg);
continue;
}
}
else {
qDebug() << QString("Warning: file %1 skipped as slot %2 not available").arg(mc.filename.c_str()).arg(mc.modelIdx + 1);
} else {
statusMsg(tr("Warning: file %1 skipped as slot %2 not available")
.arg(mc.filename.c_str()).arg(mc.modelIdx + 1), QtWarningMsg);
continue;
}
}
@@ -132,7 +147,7 @@ bool LabelsStorageFormat::load(RadioData & radioData)
QString filename = "MODELS/" + QString::fromStdString(mc.filename);
if (!loadFile(modelBuffer, filename)) {
setError(tr("Cannot extract ") + filename);
fatalMsg(tr("Cannot load %1").arg(filename));
return false;
}
@@ -143,11 +158,11 @@ bool LabelsStorageFormat::load(RadioData & radioData)
try {
if (!loadModelFromYaml(model,modelBuffer)) {
setError(tr("Cannot load ") + filename);
fatalMsg(tr("Cannot convert to yaml %1").arg(filename));
return false;
}
} catch(const std::runtime_error& e) {
setError(tr("Cannot load ") + filename + ":\n" + QString(e.what()));
fatalMsg(tr("Cannot convert to yaml %1:\n%2").arg(filename).arg(QString(e.what())));
return false;
}
@@ -170,8 +185,12 @@ bool LabelsStorageFormat::load(RadioData & radioData)
model.used = true;
modelIdx++;
progressSetValue(++steps);
statusMsg(tr("Loaded: %1").arg(filename));
}
statusMsg(tr("Loading model images..."));
for (const auto &fname : modelImages) {
if (!loadImageFile(fname, true))
return false;
@@ -179,6 +198,7 @@ bool LabelsStorageFormat::load(RadioData & radioData)
// Add the labels in the models
if (hasLabels) {
statusMsg(tr("Loading labels..."));
radioData.addLabelsFromModels();
// If no labels, add a Favorites
@@ -199,8 +219,57 @@ bool LabelsStorageFormat::write(RadioData & radioData)
// Also some are at a point in time eg calibration and
// therefore not restored to the radio from say an etx file
progressSetValue(0);
progressSetMaximum(100);
// retrieve models list + delete models + calib + write settings
int steps = 4;
bool hasLabels = getCurrentFirmware()->getCapability(HasModelLabels);
steps += (int)hasLabels;
for (const auto& model : radioData.models) {
if (!model.isEmpty())
steps += 2; // assume yaml and image for every model
}
progressSetMaximum(steps);
steps = 0;
std::list<std::string> filelist;
getFileList(filelist);
if (filelist.size()) {
// Delete all old modelxx.yml from radio MODELS folder before writing new modelxx.yml files
progressSetInfoAndMsg(tr("Deleting existing models..."));
progressSetValue(++steps);
const std::regex yml_regex("MODELS/(model([0-9]+)\\.yml)", std::regex_constants::icase);
for (const auto& f : filelist) {
std::smatch match;
if (std::regex_match(f, match, yml_regex)) {
if (match.size() == 3) {
if (!deleteFile(QString(f.c_str()))) {
fatalMsg(tr("Unable to delete file: %1").arg(QDir::toNativeSeparators(QString(f.c_str()))));
return false;
} else {
statusMsg(tr("Deleted file: %1").arg(QString(f.c_str())));
}
}
}
}
}
progressSetValue(++steps);
// may not exist on a new sd card or path
if (QFile(filename + "/RADIO/radio.yml").exists()) {
const QString settingsPath("RADIO/radio.yml");
const QString fullSettingsPath(filename % "/" % settingsPath);
const QString displaySettingsPath(QDir::toNativeSeparators(fullSettingsPath));
if (QFile(fullSettingsPath).exists()) {
progressSetInfoAndMsg(tr("Preserve radio calibration..."));
GeneralSettings gsCur;
if (loadRadioSettings(gsCur)) {
@@ -210,44 +279,32 @@ bool LabelsStorageFormat::write(RadioData & radioData)
for (int i = 0; i < CPN_MAX_INPUTS; i++) {
gsNew.inputConfig[i].calib = gsCur.inputConfig[i].calib;
}
qDebug() << "Hardware specific settings preserved";
} else {
setError("Error reading current settings from radio");
fatalMsg(tr("Error reading radio calibration from %1")
.arg(displaySettingsPath));
return false;
}
} else {
qDebug() << "Radio calibration not found" << displaySettingsPath;
}
progressSetValue(++steps);
progressSetInfoAndMsg(tr("Writing radio settings..."));
QByteArray radioSettingsBuffer;
if (!writeRadioSettingsToYaml(radioData.generalSettings, radioSettingsBuffer))
return false;
if (!writeFile(radioSettingsBuffer, "RADIO/radio.yml"))
return false;
bool hasLabels = getCurrentFirmware()->getCapability(HasModelLabels);
// fetch "MODELS/modelXX.yml"
std::list<std::string> filelist;
if (!getFileList(filelist)) {
setError(tr("Cannot list files"));
if (!writeRadioSettingsToYaml(radioData.generalSettings, radioSettingsBuffer)) {
fatalMsg(tr("Error converting radio settings to yaml"));
return false;
}
// Delete all old modelxx.yml from radio MODELS folder before writing new modelxx.yml files
const std::regex yml_regex("MODELS/(model([0-9s]+)\\.yml)", std::regex_constants::icase);
for (const auto& f : filelist) {
std::smatch match;
if (std::regex_match(f, match, yml_regex)) {
if (match.size() == 3) {
if (!deleteFile(QString(f.c_str()))) {
setError(tr("Error deleting files"));
return false;
}
}
}
if (!writeFile(radioSettingsBuffer, settingsPath)) {
fatalMsg(tr("Error writing: %1").arg(displaySettingsPath));
return false;
}
progressSetValue(++steps);
progressSetInfoAndMsg(tr("Writing models..."));
EtxModelfiles modelFiles;
QList<QString> modelImages;
@@ -256,6 +313,7 @@ bool LabelsStorageFormat::write(RadioData & radioData)
continue;
QString modelFilename;
if (hasLabels) {
std::string ymlFilename = patchFilenameToYaml(model.filename);
modelFilename = QString("MODELS/%1").arg(QString::fromStdString(ymlFilename));
@@ -265,9 +323,15 @@ bool LabelsStorageFormat::write(RadioData & radioData)
}
QByteArray modelData;
writeModelToYaml(model, modelData);
if (!writeFile(modelData, modelFilename))
if (!writeModelToYaml(model, modelData)) {
fatalMsg(tr("Error converting model to yaml: %1").arg(model.name));
return false;
}
if (!writeFile(modelData, modelFilename)) {
fatalMsg(tr("Error writing: %1").arg(QDir::toNativeSeparators(filename % "/" % modelFilename)));
return false;
}
if (!model.isBitmapEmpty()) {
const QString fname(model.getImageFilename());
@@ -279,19 +343,37 @@ bool LabelsStorageFormat::write(RadioData & radioData)
if (!writeChecklist(model))
return false;
statusMsg(tr("Model written: %1").arg(model.name));
progressSetValue(++steps);
}
progressSetInfoAndMsg(tr("Writing model images..."));
for (const auto &fname : modelImages) {
if (!writeImageFile(fname))
return false;
else
progressSetValue(++steps);
}
if (hasLabels) {
progressSetInfoAndMsg(tr("Writing labels..."));
QByteArray labelsListBuffer;
if (!writeLabelsListToYaml(radioData, labelsListBuffer) || !writeFile(labelsListBuffer, "MODELS/labels.yml"))
if (!writeLabelsListToYaml(radioData, labelsListBuffer)) {
fatalMsg(tr("Error converting labels to yaml"));
return false;
}
const QString labelsPath("MODELS/labels.yml");
if (!writeFile(labelsListBuffer, labelsPath)) {
fatalMsg(tr("Error writing: %1").arg(QDir::toNativeSeparators(filename % "/" % labelsPath)));
return false;
}
}
progressSetValue(++steps);
return true;
}
@@ -300,10 +382,8 @@ bool LabelsStorageFormat::loadChecklist(ModelData & model)
const QString fname("MODELS/" + model.getChecklistFilename());
//qDebug() << "Searching for checklist file:" << fname;
if (!loadFile(model.checklistData, fname, true)) {
setError(tr("Cannot load ") + fname);
if (!loadFile(model.checklistData, fname, true))
return false;
}
return true;
}
@@ -314,10 +394,8 @@ bool LabelsStorageFormat::writeChecklist(const ModelData & model)
const QString fname("MODELS/" + model.getChecklistFilename());
//qDebug() << "Writing checklist file:" << fname;
if (!writeFile(model.checklistData, fname)) {
setError(tr("Cannot write ") + fname);
if (!writeFile(model.checklistData, fname))
return false;
}
}
return true;
@@ -331,17 +409,17 @@ bool LabelsStorageFormat::loadRadioSettings(GeneralSettings & generalSettings)
const QString filePath(QDir::toNativeSeparators(filename + "/" + file));
if (!loadFile(radioSettingsBuffer, file)) {
setError(tr("Cannot extract %1").arg(filePath));
fatalMsg(tr("Cannot read %1").arg(filePath));
return false;
}
try {
if (!loadRadioSettingsFromYaml(generalSettings, radioSettingsBuffer)) {
setError(tr("Cannot load %1").arg(filePath));
fatalMsg(tr("Cannot convert to yaml %1").arg(filePath));
return false;
}
} catch(const std::runtime_error& e) {
setError(tr("Cannot load %1").arg(filePath) + ":\n" + QString(e.what()));
fatalMsg(tr("Cannot convert to yaml %1:\n%2").arg(filePath).arg(QString(e.what())));
return false;
}
+36 -24
View File
@@ -36,25 +36,25 @@ bool SdcardFormat::write(RadioData & radioData)
bool SdcardFormat::loadFile(QByteArray & filedata, const QString & filename, bool optional)
{
filedata.clear(); // in case buffer is being reused
QString path = this->filename + "/" + filename;
QFile file(path);
if (!file.exists()) {
if (optional) {
//qDebug() << "File not found:" << filename;
return true;
} else {
return false;
}
}
if (!file.exists()) return optional;
if (!file.open(QFile::ReadOnly)) {
setError(tr("Error opening file %1:\n%2.").arg(filename).arg(file.errorString()));
fatalMsg(tr("Error opening file %1:\n%2.").arg(filename).arg(file.errorString()));
return false;
}
filedata = file.readAll();
qDebug() << "File" << filename << "read, size:" << filedata.size();
if (filedata.isEmpty()) {
fatalMsg(tr("File empty: %1").arg(filename));
return false;
}
statusMsg(tr("File loaded: %1").arg(filename));
return true;
}
@@ -72,22 +72,22 @@ bool SdcardFormat::loadImageFile(const QString & filename, bool optional)
// QFile::copy fails if destination file exists
if (destfile.exists()) {
if (destfile.remove()) {
qDebug() << "Deleted file from image cache" << filename;
//qDebug() << "Deleted file from image cache" << filename;
} else {
qDebug() << "Unable to delete cached" << filename;
fatalMsg(tr("Unable to delete cached").arg(filename));
return false;
}
}
if (file.copy(Helpers::getImagesCacheDir() % "/" % filename)) {
qDebug() << "Cached image" << filename;
// qDebug() << "Cached image" << filename;
} else {
qDebug() << "Failed to cache image" << filename;
fatalMsg(tr("Failed to cache image: %1").arg(filename));
return false;
}
} else {
qDebug() << "Failed to cache" << filename << ". Cache directory" << Helpers::getImagesCacheDir()
<< "does not exist.";
fatalMsg(tr("No application image cache"));
return false;
}
return true;
@@ -99,13 +99,19 @@ bool SdcardFormat::writeFile(const QByteArray & data, const QString & filename)
QFile file(path);
if (!file.open(QFile::WriteOnly)) {
setError(tr("Error opening file %1 in write mode:\n%2.").arg(path).arg(file.errorString()));
fatalMsg(tr("Error opening file %1 in write mode:\n%2.").arg(path).arg(file.errorString()));
return false;
}
file.write(data.data(), data.size());
qint64 writesz = file.write(data.data(), data.size());
file.close();
qDebug() << "File" << path << "written, size:" << data.size();
if (writesz != data.size()) {
fatalMsg(tr("Error writing file %1: wrote %2 of %3 bytes").arg(path).arg(writesz).arg(data.size()));
return false;
}
statusMsg(tr("File written: %1").arg(path));
return true;
}
@@ -121,20 +127,26 @@ bool SdcardFormat::writeImageFile(const QString & filename)
if (destinfo.exists()) {
// for performance reasons avoid overwriting the same image
if (destinfo.size() == srcinfo.size()) {
qDebug() << "Image file" << filename << "skipped as same size";
statusMsg(tr("Duplicate image file: %1 skipped").arg(filename));
return true;
}
// Size differs, remove the existing file before copying
if (!QFile::remove(destpath)) {
fatalMsg(tr("Error removing existing image file: %1").arg(filename));
return false;
}
}
if (!QFile(srcpath).copy(destpath)) {
setError(tr("Error writing image file: %1").arg(filename));
fatalMsg(tr("Error writing image file: %1").arg(filename));
return false;
}
qDebug() << "File" << destpath << "written, size:" << srcinfo.size();
statusMsg(tr("Image written: %1").arg(filename));
return true;
}
fatalMsg(tr("No application image cache"));
return false;
}
@@ -158,11 +170,11 @@ bool SdcardFormat::deleteFile(const QString & filename)
QString path = this->filename + "/" + filename;
if (!QFile::remove(path)) {
setError(tr("Error deleting file %1").arg(path));
fatalMsg(tr("Error deleting file: %1").arg(path));
return false;
}
qDebug() << "File" << path << "deleted";
statusMsg(tr("Deleted file: %1").arg(path));
return true;
}
+47
View File
@@ -60,6 +60,50 @@ void unregisterStorageFactories()
delete factory;
}
void StorageFormat::statusMsg(const QString & text, const int & type,
const bool richText, const bool updateLast)
{
if (_progress)
_progress->addMessage(text, type, richText, updateLast);
if (type == QtCriticalMsg || type == QtFatalMsg)
setError(text);
else if (type == QtWarningMsg)
setWarning(text);
}
void StorageFormat::fatalMsg(const QString & text, const bool richText)
{
statusMsg(text, QtFatalMsg, richText);
}
void StorageFormat::progressSetInfoAndMsg(const QString & text, const int & type, const bool richText)
{
progressSetInfo(text);
statusMsg(text, type, richText);
}
void StorageFormat::progressSetInfo(const QString & msg)
{
if (_progress) _progress->setInfo(msg);
}
void StorageFormat::progressSetLock(const bool lock)
{
if (_progress) _progress->lock(lock);
}
void StorageFormat::progressSetMaximum(const int max)
{
if (_progress) _progress->setMaximum(max);
}
void StorageFormat::progressSetValue(const int val)
{
if (_progress) _progress->setValue(val);
}
bool Storage::load(RadioData & radioData)
{
if (!fileExists())
@@ -69,6 +113,8 @@ bool Storage::load(RadioData & radioData)
StorageFormat *format = getStorageFormat();
if (format) {
format->setProgress(_progress);
if (format->load(radioData)) {
board = format->getBoard();
setWarning(format->warning());
@@ -89,6 +135,7 @@ bool Storage::write(RadioData & radioData)
StorageFormat *format = getStorageFormat();
if (format) {
format->setProgress(_progress);
ret = format->write(radioData);
delete format;
}
+16 -1
View File
@@ -22,6 +22,8 @@
#pragma once
#include "radiodata.h"
#include "progressdialog.h"
#include "progresswidget.h"
#include <QtCore>
#include <QString>
@@ -46,7 +48,8 @@ class StorageFormat
StorageFormat(const QString & filename, uint8_t version=0):
filename(filename),
version(version),
board(Board::BOARD_UNKNOWN)
board(Board::BOARD_UNKNOWN),
_progress(nullptr)
{
}
virtual ~StorageFormat() {}
@@ -55,6 +58,8 @@ class StorageFormat
virtual bool write(RadioData & radioData) = 0;
virtual bool writeModel(const RadioData & radioData, const int modelIndex) { return false; }
void setProgress(ProgressWidget * progress) { _progress = progress; }
QString error() {
return _error;
}
@@ -83,11 +88,21 @@ class StorageFormat
_warning = warning;
}
void statusMsg(const QString & text, const int & type = QtInfoMsg,
const bool richText = false, const bool updateLast = false);
void fatalMsg(const QString & text, const bool richText = false);
void progressSetInfoAndMsg(const QString & text, const int & type = QtInfoMsg, const bool richText = false);
void progressSetInfo(const QString & msg);
void progressSetLock(const bool lock);
void progressSetMaximum(const int max);
void progressSetValue(const int val);
QString filename;
uint8_t version;
QString _error;
QString _warning;
Board::Type board;
ProgressWidget * _progress;
};
class StorageFactory