diff --git a/.gitignore b/.gitignore index 53baecf8..1f63de72 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* .DS_Store +/build diff --git a/QLog.pro b/QLog.pro index f4cd0930..6cca6185 100644 --- a/QLog.pro +++ b/QLog.pro @@ -55,6 +55,7 @@ CONFIG += c++11 force_debug_info CONFIG *= link_pkgconfig SOURCES += \ + core/ADIFFileMonitor.cpp \ core/AlertEvaluator.cpp \ core/AppGuard.cpp \ core/CallbookManager.cpp \ @@ -202,6 +203,7 @@ SOURCES += \ ui/component/SwitchButton.cpp HEADERS += \ + core/ADIFFileMonitor.h \ core/AlertEvaluator.h \ core/AppGuard.h \ core/CallbookManager.h \ diff --git a/core/ADIFFileMonitor.cpp b/core/ADIFFileMonitor.cpp new file mode 100644 index 00000000..71c461a7 --- /dev/null +++ b/core/ADIFFileMonitor.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include + +#include "ADIFFileMonitor.h" +#include "core/LogParam.h" +#include "core/debug.h" +#include "data/StationProfile.h" +#include "logformat/LogFormat.h" + +MODULE_IDENTIFICATION("qlog.core.adiffilemonitor"); + +ADIFFileMonitor::ADIFFileMonitor(QObject *parent) + : QObject(parent) +{ + FCT_IDENTIFICATION; +} + +ADIFFileMonitor::~ADIFFileMonitor() +{ + FCT_IDENTIFICATION; +} + +QList ADIFFileMonitor::getEnabledSlots(ImportFrequency freq) const +{ + FCT_IDENTIFICATION; + + QList result; + + for ( int i = 0; i < MAX_SLOTS; ++i ) + { + SlotConfig cfg; + cfg.enabled = LogParam::getADIFMonitorEnabled(i); + cfg.path = LogParam::getADIFMonitorPath(i); + cfg.frequency = static_cast(LogParam::getADIFMonitorFrequency(i)); + cfg.profile = LogParam::getADIFMonitorProfile(i); + + if ( cfg.enabled && cfg.frequency == freq && !cfg.path.isEmpty() ) + result.append(cfg); + } + + return result; +} + +LogFormat::duplicateQSOBehaviour ADIFFileMonitor::skipAllDuplicates(QSqlRecord *, QSqlRecord *) +{ + return LogFormat::SKIP_ALL; +} + +void ADIFFileMonitor::runImportForSlot(const SlotConfig &slot) +{ + FCT_IDENTIFICATION; + + qCDebug(runtime) << "ADIF Monitor importing:" << slot.path; + + QFile file(slot.path); + + if ( !file.exists() ) + { + qCWarning(runtime) << "ADIF Monitor: file not found:" << slot.path; + return; + } + + if ( !file.open(QFile::ReadOnly | QFile::Text) ) + { + qCWarning(runtime) << "ADIF Monitor: cannot open file:" << slot.path; + return; + } + + QTextStream in(&file); + + LogFormat *format = LogFormat::open("adi", in); + + if ( !format ) + { + qCWarning(runtime) << "ADIF Monitor: failed to open as ADIF:" << slot.path; + return; + } + + format->setDuplicateQSOCallback(skipAllDuplicates); + + StationProfile stationProfile; + + if ( !slot.profile.isEmpty() ) + stationProfile = StationProfilesManager::instance()->getProfile(slot.profile); + + if ( stationProfile == StationProfile() ) + stationProfile = StationProfilesManager::instance()->getCurProfile1(); + + const StationProfile *profile = ( stationProfile != StationProfile() ) ? &stationProfile : nullptr; + + unsigned long warnings = 0; + unsigned long errors = 0; + QString logStream; + QTextStream logOut(&logStream); + + int count = format->runImport(logOut, profile, &warnings, &errors); + + if (count > 0) + { + QString s; + QString report = QObject::tr("Import File: ") + slot.path + "
" + + QObject::tr("Imported: %n contact(s)", "", count) + "
" + + QObject::tr("Warning(s): %n", "", warnings) + "
" + + QObject::tr("Error(s): %n", "", errors); + + QMessageBox msgBox; + + msgBox.setWindowTitle(tr("Import Result")); + msgBox.setText(report); + msgBox.setDetailedText(s); + msgBox.setIcon(QMessageBox::Information); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + + QSpacerItem* horizontalSpacer = new QSpacerItem(500, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); + QGridLayout* layout = qobject_cast(msgBox.layout()); + if ( !layout ) + { + qWarning() << "Layout is null"; + delete horizontalSpacer; + return; + } + layout->addItem(horizontalSpacer, layout->rowCount(), 0, 1, layout->columnCount()); + + msgBox.exec(); + + } + delete format; + + qCDebug(runtime) << "ADIF Monitor import finished. Warnings:" << warnings << "Errors:" << errors; +} + +void ADIFFileMonitor::runStartupImports() +{ + FCT_IDENTIFICATION; + + const QList fileSlots = getEnabledSlots(STARTUP); + + for ( const SlotConfig &cfg : fileSlots ) + runImportForSlot(cfg); +} + +void ADIFFileMonitor::runShutdownImports() +{ + FCT_IDENTIFICATION; + + const QList fileSlots = getEnabledSlots(SHUTDOWN); + + for ( const SlotConfig &cfg : fileSlots ) + runImportForSlot(cfg); +} diff --git a/core/ADIFFileMonitor.h b/core/ADIFFileMonitor.h new file mode 100644 index 00000000..8c328adb --- /dev/null +++ b/core/ADIFFileMonitor.h @@ -0,0 +1,40 @@ +#ifndef QLOG_CORE_ADIFFILEMONITOR_H +#define QLOG_CORE_ADIFFILEMONITOR_H + +#include +#include +#include "logformat/LogFormat.h" + +class ADIFFileMonitor : public QObject +{ + Q_OBJECT + +public: + static const int MAX_SLOTS = 4; + + enum ImportFrequency { + DISABLED = 0, + STARTUP = 1, + SHUTDOWN = 2 + }; + + explicit ADIFFileMonitor(QObject *parent = nullptr); + ~ADIFFileMonitor(); + + void runStartupImports(); + void runShutdownImports(); + +private: + struct SlotConfig { + bool enabled = false; + QString path; + ImportFrequency frequency = DISABLED; + QString profile; + }; + + QList getEnabledSlots(ImportFrequency freq) const; + void runImportForSlot(const SlotConfig &slot); + static LogFormat::duplicateQSOBehaviour skipAllDuplicates(QSqlRecord *, QSqlRecord *); +}; + +#endif // QLOG_CORE_ADIFFILEMONITOR_H diff --git a/core/LogParam.cpp b/core/LogParam.cpp index 5432f486..a16b8085 100644 --- a/core/LogParam.cpp +++ b/core/LogParam.cpp @@ -1247,6 +1247,46 @@ void LogParam::removeMainWindowBandmapWidgets() removeParamGroup("mainwindow/bandmapwidgets"); } +bool LogParam::getADIFMonitorEnabled(int slot) +{ + return getParam(QString("adifmonitor/slot%1/enabled").arg(slot), false).toBool(); +} + +void LogParam::setADIFMonitorEnabled(int slot, bool enabled) +{ + setParam(QString("adifmonitor/slot%1/enabled").arg(slot), enabled); +} + +QString LogParam::getADIFMonitorPath(int slot) +{ + return getParam(QString("adifmonitor/slot%1/path").arg(slot)).toString(); +} + +void LogParam::setADIFMonitorPath(int slot, const QString &path) +{ + setParam(QString("adifmonitor/slot%1/path").arg(slot), path); +} + +int LogParam::getADIFMonitorFrequency(int slot) +{ + return getParam(QString("adifmonitor/slot%1/frequency").arg(slot), 0).toInt(); +} + +void LogParam::setADIFMonitorFrequency(int slot, int frequency) +{ + setParam(QString("adifmonitor/slot%1/frequency").arg(slot), frequency); +} + +QString LogParam::getADIFMonitorProfile(int slot) +{ + return getParam(QString("adifmonitor/slot%1/profile").arg(slot)).toString(); +} + +void LogParam::setADIFMonitorProfile(int slot, const QString &profile) +{ + setParam(QString("adifmonitor/slot%1/profile").arg(slot), profile); +} + bool LogParam::setParam(const QString &name, const QVariant &value) { FCT_IDENTIFICATION; diff --git a/core/LogParam.h b/core/LogParam.h index 9eeef242..730d86dd 100644 --- a/core/LogParam.h +++ b/core/LogParam.h @@ -357,6 +357,18 @@ class LogParam : public QObject static void setSourcePlatform(const QString &platform); static void removeSourcePlatform(); + /********************** + * ADIF File Monitor + **********************/ + static bool getADIFMonitorEnabled(int slot); + static void setADIFMonitorEnabled(int slot, bool enabled); + static QString getADIFMonitorPath(int slot); + static void setADIFMonitorPath(int slot, const QString &path); + static int getADIFMonitorFrequency(int slot); + static void setADIFMonitorFrequency(int slot, int frequency); + static QString getADIFMonitorProfile(int slot); + static void setADIFMonitorProfile(int slot, const QString &profile); + /************** * Main Window *************/ diff --git a/ui/MainWindow.cpp b/ui/MainWindow.cpp index 5fb7b679..7c1ac5c8 100644 --- a/ui/MainWindow.cpp +++ b/ui/MainWindow.cpp @@ -461,6 +461,11 @@ MainWindow::MainWindow(QWidget* parent) : //restoreConnectionStates(); setupActivitiesMenu(); + + /************************/ + /* ADIF File Monitor */ + /************************/ + adifMonitor.runStartupImports(); } void MainWindow::closeEvent(QCloseEvent* event) @@ -512,6 +517,11 @@ void MainWindow::closeEvent(QCloseEvent* event) LogParam::removeBandmapWidgetGroup(orphanConfig); } + /****************************/ + /* ADIF File Monitor */ + /****************************/ + adifMonitor.runShutdownImports(); + // Save unsaved widget states const auto allWidgets = findChildren(); for ( QWidget *w : allWidgets ) diff --git a/ui/MainWindow.h b/ui/MainWindow.h index b8b7a114..1f74cc75 100644 --- a/ui/MainWindow.h +++ b/ui/MainWindow.h @@ -6,6 +6,7 @@ #include #include "ui/StatisticsWidget.h" #include "core/NetworkNotification.h" +#include "core/ADIFFileMonitor.h" #include "core/AlertEvaluator.h" #include "core/PropConditions.h" #include "service/clublog/ClubLog.h" @@ -109,6 +110,7 @@ private slots: QPushButton *themeButton; StatisticsWidget* stats; NetworkNotification networknotification; + ADIFFileMonitor adifMonitor; AlertEvaluator alertEvaluator; PropConditions *conditions; bool isFusionStyle; diff --git a/ui/SettingsDialog.cpp b/ui/SettingsDialog.cpp index f5d77a33..3b7fffbb 100644 --- a/ui/SettingsDialog.cpp +++ b/ui/SettingsDialog.cpp @@ -40,6 +40,7 @@ #include "service/cloudlog/Cloudlog.h" #include "ui/RigctldAdvancedDialog.h" #include "cwkey/drivers/CWWinKey.h" +#include "core/ADIFFileMonitor.h" #define STACKED_WIDGET_SERIAL_SETTING 0 #define STACKED_WIDGET_NETWORK_SETTING 1 @@ -329,6 +330,29 @@ SettingsDialog::SettingsDialog(MainWindow *parent) : generateMembershipCheckboxes(); + /**********************/ + /* ADIF File Monitor */ + /**********************/ + { + const QStringList profiles = stationProfManager->profileNameList(); + const QList profileCombos = { + ui->adifMonitorProfile0, + ui->adifMonitorProfile1, + ui->adifMonitorProfile2, + ui->adifMonitorProfile3 + }; + for ( QComboBox *combo : profileCombos ) + { + combo->addItem(tr("(none)"), QString()); + for ( const QString &p : profiles ) + combo->addItem(p, p); + } + connect(ui->adifMonitorBrowse0, &QToolButton::clicked, this, &SettingsDialog::adifMonitorBrowse0); + connect(ui->adifMonitorBrowse1, &QToolButton::clicked, this, &SettingsDialog::adifMonitorBrowse1); + connect(ui->adifMonitorBrowse2, &QToolButton::clicked, this, &SettingsDialog::adifMonitorBrowse2); + connect(ui->adifMonitorBrowse3, &QToolButton::clicked, this, &SettingsDialog::adifMonitorBrowse3); + } + readSettings(); } @@ -2622,6 +2646,26 @@ void SettingsDialog::readSettings() ui->notifSpotAlertEdit->setText(NetworkNotification::getNotifSpotAlertAddrs()); ui->notifRigEdit->setText(NetworkNotification::getNotifRigStateAddrs()); + /**********************/ + /* ADIF File Monitor */ + /**********************/ + { + const QList enabledChecks = { ui->adifMonitorEnabled0, ui->adifMonitorEnabled1, ui->adifMonitorEnabled2, ui->adifMonitorEnabled3 }; + const QList pathEdits = { ui->adifMonitorPath0, ui->adifMonitorPath1, ui->adifMonitorPath2, ui->adifMonitorPath3 }; + const QList freqCombos = { ui->adifMonitorFrequency0, ui->adifMonitorFrequency1, ui->adifMonitorFrequency2, ui->adifMonitorFrequency3 }; + const QList profCombos = { ui->adifMonitorProfile0, ui->adifMonitorProfile1, ui->adifMonitorProfile2, ui->adifMonitorProfile3 }; + + for ( int i = 0; i < ADIFFileMonitor::MAX_SLOTS; ++i ) + { + enabledChecks[i]->setChecked(LogParam::getADIFMonitorEnabled(i)); + pathEdits[i]->setText(LogParam::getADIFMonitorPath(i)); + freqCombos[i]->setCurrentIndex(LogParam::getADIFMonitorFrequency(i)); + const QString savedProfile = LogParam::getADIFMonitorProfile(i); + const int profileIdx = profCombos[i]->findData(savedProfile); + profCombos[i]->setCurrentIndex(profileIdx >= 0 ? profileIdx : 0); + } + } + /*******/ /* GUI */ /*******/ @@ -2753,6 +2797,24 @@ void SettingsDialog::writeSettings() NetworkNotification::saveNotifSpotAlertAddrs(ui->notifSpotAlertEdit->text()); NetworkNotification::saveNotifRigStateAddrs(ui->notifRigEdit->text()); + /**********************/ + /* ADIF File Monitor */ + /**********************/ + { + const QList enabledChecks = { ui->adifMonitorEnabled0, ui->adifMonitorEnabled1, ui->adifMonitorEnabled2, ui->adifMonitorEnabled3 }; + const QList pathEdits = { ui->adifMonitorPath0, ui->adifMonitorPath1, ui->adifMonitorPath2, ui->adifMonitorPath3 }; + const QList freqCombos = { ui->adifMonitorFrequency0, ui->adifMonitorFrequency1, ui->adifMonitorFrequency2, ui->adifMonitorFrequency3 }; + const QList profCombos = { ui->adifMonitorProfile0, ui->adifMonitorProfile1, ui->adifMonitorProfile2, ui->adifMonitorProfile3 }; + + for ( int i = 0; i < ADIFFileMonitor::MAX_SLOTS; ++i ) + { + LogParam::setADIFMonitorEnabled(i, enabledChecks[i]->isChecked()); + LogParam::setADIFMonitorPath(i, pathEdits[i]->text()); + LogParam::setADIFMonitorFrequency(i, freqCombos[i]->currentIndex()); + LogParam::setADIFMonitorProfile(i, profCombos[i]->currentData().toString()); + } + } + /*******/ /* GUI */ /*******/ @@ -3065,6 +3127,46 @@ void SettingsDialog::updateCountyCompleter(int dxcc) ui->stationCountyEdit->setCompleter(countyCompleter); } +void SettingsDialog::adifMonitorBrowse0() +{ + FCT_IDENTIFICATION; + QString filename = QFileDialog::getOpenFileName(this, tr("Select ADIF File"), + QDir::homePath(), + tr("ADIF Files (*.adi *.ADI)")); + if ( !filename.isEmpty() ) + ui->adifMonitorPath0->setText(filename); +} + +void SettingsDialog::adifMonitorBrowse1() +{ + FCT_IDENTIFICATION; + QString filename = QFileDialog::getOpenFileName(this, tr("Select ADIF File"), + QDir::homePath(), + tr("ADIF Files (*.adi *.ADI)")); + if ( !filename.isEmpty() ) + ui->adifMonitorPath1->setText(filename); +} + +void SettingsDialog::adifMonitorBrowse2() +{ + FCT_IDENTIFICATION; + QString filename = QFileDialog::getOpenFileName(this, tr("Select ADIF File"), + QDir::homePath(), + tr("ADIF Files (*.adi *.ADI)")); + if ( !filename.isEmpty() ) + ui->adifMonitorPath2->setText(filename); +} + +void SettingsDialog::adifMonitorBrowse3() +{ + FCT_IDENTIFICATION; + QString filename = QFileDialog::getOpenFileName(this, tr("Select ADIF File"), + QDir::homePath(), + tr("ADIF Files (*.adi *.ADI)")); + if ( !filename.isEmpty() ) + ui->adifMonitorPath3->setText(filename); +} + SettingsDialog::~SettingsDialog() { FCT_IDENTIFICATION; diff --git a/ui/SettingsDialog.h b/ui/SettingsDialog.h index dfdadd36..02c4532c 100644 --- a/ui/SettingsDialog.h +++ b/ui/SettingsDialog.h @@ -118,6 +118,10 @@ public slots: void assignedKeyChanged(int); void testWebLookupURL(); void joinMulticastChanged(int); + void adifMonitorBrowse0(); + void adifMonitorBrowse1(); + void adifMonitorBrowse2(); + void adifMonitorBrowse3(); void adjustWSJTXMulticastAddrTextColor(); void hrdlogSettingChanged(); void clublogSettingChanged(); diff --git a/ui/SettingsDialog.ui b/ui/SettingsDialog.ui index ba68dcdd..6bbca3c8 100644 --- a/ui/SettingsDialog.ui +++ b/ui/SettingsDialog.ui @@ -4667,6 +4667,279 @@ + + + + ADIF File Monitor + + + + + + Enabled + + + + + + + File + + + + + + + When + + + + + + + Station Profile + + + + + + + Enable monitoring of this ADIF file + + + + + + + + + + Path to the ADIF file to monitor and import + + + Select ADIF file... + + + + + + + Browse for ADIF file + + + ... + + + + + + + When to import this file + + + + Disabled + + + + + Startup + + + + + Shutdown + + + + + + + + Default station profile to apply when importing (used when the ADIF record does not contain station data) + + + + + + + Enable monitoring of this ADIF file + + + + + + + + + + Path to the ADIF file to monitor and import + + + Select ADIF file... + + + + + + + Browse for ADIF file + + + ... + + + + + + + When to import this file + + + + Disabled + + + + + Startup + + + + + Shutdown + + + + + + + + Default station profile to apply when importing (used when the ADIF record does not contain station data) + + + + + + + Enable monitoring of this ADIF file + + + + + + + + + + Path to the ADIF file to monitor and import + + + Select ADIF file... + + + + + + + Browse for ADIF file + + + ... + + + + + + + When to import this file + + + + Disabled + + + + + Startup + + + + + Shutdown + + + + + + + + Default station profile to apply when importing (used when the ADIF record does not contain station data) + + + + + + + Enable monitoring of this ADIF file + + + + + + + + + + Path to the ADIF file to monitor and import + + + Select ADIF file... + + + + + + + Browse for ADIF file + + + ... + + + + + + + When to import this file + + + + Disabled + + + + + Startup + + + + + Shutdown + + + + + + + + Default station profile to apply when importing (used when the ADIF record does not contain station data) + + + + + +