From d34582686bc3f35b2d4756711c4371fa25153789 Mon Sep 17 00:00:00 2001 From: Neil Horne <15316949+elecpower@users.noreply.github.com> Date: Tue, 30 Jun 2026 11:42:21 +1000 Subject: [PATCH] feat(cpn): add detailed progress to read and write radio (#7450) --- companion/src/flash/radiointerface.cpp | 26 ++- companion/src/mainwindow.cpp | 83 +++----- companion/src/mainwindow.h | 1 - companion/src/mdichild.cpp | 71 +++++-- companion/src/mdichild.h | 5 +- companion/src/modeledit/setup.cpp | 3 +- companion/src/progress/progresswidget.cpp | 21 +- companion/src/progress/progresswidget.h | 2 + companion/src/storage/etx.cpp | 45 +++-- companion/src/storage/labeled.cpp | 230 +++++++++++++++------- companion/src/storage/sdcard.cpp | 60 +++--- companion/src/storage/storage.cpp | 47 +++++ companion/src/storage/storage.h | 17 +- 13 files changed, 410 insertions(+), 201 deletions(-) diff --git a/companion/src/flash/radiointerface.cpp b/companion/src/flash/radiointerface.cpp index c7cba553a..5b2b74b65 100644 --- a/companion/src/flash/radiointerface.cpp +++ b/companion/src/flash/radiointerface.cpp @@ -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; } diff --git a/companion/src/mainwindow.cpp b/companion/src/mainwindow.cpp index 3f44ff1fb..5d9ce0345 100644 --- a/companion/src/mainwindow.cpp +++ b/companion/src/mainwindow.cpp @@ -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() diff --git a/companion/src/mainwindow.h b/companion/src/mainwindow.h index 2bc73bb92..927a06b37 100644 --- a/companion/src/mainwindow.h +++ b/companion/src/mainwindow.h @@ -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); diff --git a/companion/src/mdichild.cpp b/companion/src/mdichild.cpp index e6b4d849d..a7a77f116 100644 --- a/companion/src/mdichild.cpp +++ b/companion/src/mdichild.cpp @@ -34,6 +34,8 @@ #include "filtereditemmodels.h" #include "labels.h" #include "firmwares/edgetx/edgetxinterface.h" +#include "progress/progressdialog.h" +#include "progress/progresswidget.h" #include #include @@ -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!")); } } diff --git a/companion/src/mdichild.h b/companion/src/mdichild.h index 49c58abb9..a33132675 100644 --- a/companion/src/mdichild.h +++ b/companion/src/mdichild.h @@ -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(); diff --git a/companion/src/modeledit/setup.cpp b/companion/src/modeledit/setup.cpp index 0394869e3..780b5b861 100644 --- a/companion/src/modeledit/setup.cpp +++ b/companion/src/modeledit/setup.cpp @@ -820,5 +820,6 @@ void SetupPanel::loadImagePreview() else ui->imagePreview->clear(); - } + } else + ui->imagePreview->clear(); } diff --git a/companion/src/progress/progresswidget.cpp b/companion/src/progress/progresswidget.cpp index 7636f320b..cc2a06352 100644 --- a/companion/src/progress/progresswidget.cpp +++ b/companion/src/progress/progresswidget.cpp @@ -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("").arg(color) % text % "
", 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) diff --git a/companion/src/progress/progresswidget.h b/companion/src/progress/progresswidget.h index 347d7cc59..63da99ddd 100644 --- a/companion/src/progress/progresswidget.h +++ b/companion/src/progress/progresswidget.h @@ -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; }; diff --git a/companion/src/storage/etx.cpp b/companion/src/storage/etx.cpp index 504c2655a..55904b1e8 100644 --- a/companion/src/storage/etx.cpp +++ b/companion/src/storage/etx.cpp @@ -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& filelist) if (count == 0) return false; mz_zip_archive_file_stat file_stat; + for (int i=0; i @@ -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 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 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 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 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; } diff --git a/companion/src/storage/sdcard.cpp b/companion/src/storage/sdcard.cpp index f73e4bb41..ac3209376 100644 --- a/companion/src/storage/sdcard.cpp +++ b/companion/src/storage/sdcard.cpp @@ -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; } diff --git a/companion/src/storage/storage.cpp b/companion/src/storage/storage.cpp index 86b016d50..821b18887 100644 --- a/companion/src/storage/storage.cpp +++ b/companion/src/storage/storage.cpp @@ -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; } diff --git a/companion/src/storage/storage.h b/companion/src/storage/storage.h index 8e0a23562..5e6fa9a3a 100644 --- a/companion/src/storage/storage.h +++ b/companion/src/storage/storage.h @@ -22,6 +22,8 @@ #pragma once #include "radiodata.h" +#include "progressdialog.h" +#include "progresswidget.h" #include #include @@ -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