From ce622487ca287ea700cfd211840723695f8cff23 Mon Sep 17 00:00:00 2001 From: Brian Moran <brian@trucentive.com> Date: Sat, 18 Feb 2023 10:00:18 -0800 Subject: [PATCH 01/14] Fox should not log hound again if they've been logged before. Call is still noted in FoxQSO however, with Dup: instead of Log: (K7AR) --- widgets/mainwindow.cpp | 47 +++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 34a4406f6..b8b8ba368 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -9606,26 +9606,35 @@ list2Done: } if(hc1!="") { - // Log this QSO! - auto QSO_time = QDateTime::currentDateTimeUtc (); - m_hisCall=hc1; - m_hisGrid=m_foxQSO[hc1].grid; - m_rptSent=m_foxQSO[hc1].sent; - m_rptRcvd=m_foxQSO[hc1].rcvd; - if (!m_foxLogWindow) on_fox_log_action_triggered (); - if (m_logBook.fox_log ()->add_QSO (QSO_time, m_hisCall, m_hisGrid, m_rptSent, m_rptRcvd, m_lastBand)) - { - writeFoxQSO (QString {" Log: %1 %2 %3 %4 %5"}.arg (m_hisCall).arg (m_hisGrid) - .arg (m_rptSent).arg (m_rptRcvd).arg (m_lastBand)); - on_logQSOButton_clicked (); - m_foxRateQueue.enqueue (now); //Add present time in seconds - //to Rate queue. - QTimer::singleShot (13000, [=] { - m_foxQSOinProgress.removeOne(hc1); //Remove from In Progress window - updateFoxQSOsInProgressDisplay(); //Update InProgress display after Tx is complete - }); + auto already_logged = m_loggedByFox[hc1].contains(m_lastBand + " "); // already logged this call on this band? + + if (!already_logged) { // Log this QSO! + auto QSO_time = QDateTime::currentDateTimeUtc (); + m_hisCall=hc1; + m_hisGrid=m_foxQSO[hc1].grid; + m_rptSent=m_foxQSO[hc1].sent; + m_rptRcvd=m_foxQSO[hc1].rcvd; + if (!m_foxLogWindow) on_fox_log_action_triggered (); + if (m_logBook.fox_log ()->add_QSO (QSO_time, m_hisCall, m_hisGrid, m_rptSent, m_rptRcvd, m_lastBand)) + { + writeFoxQSO (QString {" Log: %1 %2 %3 %4 %5"}.arg (m_hisCall).arg (m_hisGrid) + .arg (m_rptSent).arg (m_rptRcvd).arg (m_lastBand)); + on_logQSOButton_clicked (); + m_foxRateQueue.enqueue (now); //Add present time in seconds + //to Rate queue. + QTimer::singleShot (13000, [=] { + m_foxQSOinProgress.removeOne(hc1); //Remove from In Progress window + updateFoxQSOsInProgressDisplay(); //Update InProgress display after Tx is complete + }); + } + m_loggedByFox[hc1] += (m_lastBand + " "); + } + else + { + // note that this is a duplicate + writeFoxQSO(QString{" Dup: %1 %2 %3 %4 %5"}.arg(m_hisCall).arg(m_hisGrid) + .arg(m_rptSent).arg(m_rptRcvd).arg(m_lastBand)); } - m_loggedByFox[hc1] += (m_lastBand + " "); } if(i<n2 and fm=="") { From 969411cd3997d24cdd7d2b099c1fbb918709e50d Mon Sep 17 00:00:00 2001 From: Brian Moran <brian@trucentive.com> Date: Tue, 21 Feb 2023 08:58:05 -0800 Subject: [PATCH 02/14] when changing bands as Fox, clear the hound queues --- widgets/mainwindow.cpp | 29 +++++++++++++++++++---------- widgets/mainwindow.h | 3 ++- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index b8b8ba368..1f5c80dca 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -7572,6 +7572,9 @@ void MainWindow::band_changed (Frequency f) } setRig (f); setXIT (ui->TxFreqSpinBox->value ()); + + // when changing bands, don't preserve the Fox queues + FoxReset("BandChange"); } } @@ -9207,6 +9210,20 @@ void MainWindow::on_sbMax_dB_valueChanged(int n) t = t.asprintf(" Max_dB %d",m_max_dB); writeFoxQSO(t); } +void MainWindow::FoxReset(QString reason="") +{ + QFile f(m_config.temp_dir().absoluteFilePath("houndcallers.txt")); + f.remove(); + ui->decodedTextBrowser->setText(""); + ui->houndQueueTextBrowser->setText(""); + ui->foxTxListTextBrowser->setText(""); + + m_houndQueue.clear(); + m_foxQSO.clear(); + m_foxQSOinProgress.clear(); + if (reason != "") writeFoxQSO(" " + reason); + writeFoxQSO(" Reset"); +} void MainWindow::on_pbFoxReset_clicked() { @@ -9214,16 +9231,7 @@ void MainWindow::on_pbFoxReset_clicked() auto button = MessageBox::query_message (this, tr ("Confirm Reset"), tr ("Are you sure you want to clear the QSO queues?")); if(button == MessageBox::Yes) { - QFile f(m_config.temp_dir().absoluteFilePath("houndcallers.txt")); - f.remove(); - ui->decodedTextBrowser->setText(""); - ui->houndQueueTextBrowser->setText(""); - ui->foxTxListTextBrowser->setText(""); - - m_houndQueue.clear(); - m_foxQSO.clear(); - m_foxQSOinProgress.clear(); - writeFoxQSO(" Reset"); + FoxReset(); } } @@ -9347,6 +9355,7 @@ void MainWindow::selectHound(QString line, bool bTopQueue) * <Enter> is equivalent to double-clicking on the top-most line. */ if(line.length()==0) return; + if(line.length() < 6) return; QString houndCall=line.split(" ",SkipEmptyParts).at(0); // Don't add a call already enqueued or in QSO diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index a2558fe81..3243e1e3f 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -326,12 +326,13 @@ private slots: void on_sbF_High_valueChanged(int n); void chk_FST4_freq_range(); void on_pbFoxReset_clicked(); + void FoxReset(QString reason); void on_comboBoxHoundSort_activated (int index); void not_GA_warning_message (); void checkMSK144ContestType(); void on_pbBestSP_clicked(); void on_RoundRobin_currentTextChanged(QString text); - void setTxMsg(int n); + void setTxMsg(int n); bool stdCall(QString const& w); void remote_configure (QString const& mode, quint32 frequency_tolerance, QString const& submode , bool fast_mode, quint32 tr_period, quint32 rx_df, QString const& dx_call From 663ed600132d480ede80df449c78eefaafb4125b Mon Sep 17 00:00:00 2001 From: Brian Moran <brian@trucentive.com> Date: Tue, 7 Mar 2023 10:55:40 -0800 Subject: [PATCH 03/14] once fox has been reset, don't process decodes until decoding period ended --- widgets/mainwindow.cpp | 10 ++++++++++ widgets/mainwindow.h | 1 + 2 files changed, 11 insertions(+) diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 1f5c80dca..44cbc28ef 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -9221,6 +9221,7 @@ void MainWindow::FoxReset(QString reason="") m_houndQueue.clear(); m_foxQSO.clear(); m_foxQSOinProgress.clear(); + m_discard_decoded_hounds_this_cycle = true; // discard decoded messages until the next cycle if (reason != "") writeFoxQSO(" " + reason); writeFoxQSO(" Reset"); } @@ -9400,6 +9401,15 @@ void MainWindow::houndCallers() * Distance, Age, and Continent) to a list, sort the list by specified criteria, * and display the top N_Hounds entries in the left text window. */ + // if frequency was changed in the middle of an interval, there's a flag set to ignore the decodes. Reset it here + // + + if (m_discard_decoded_hounds_this_cycle) + { + m_discard_decoded_hounds_this_cycle = false; // + return; // don't use these decodes + } + QFile f(m_config.temp_dir().absoluteFilePath("houndcallers.txt")); if(f.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream s(&f); diff --git a/widgets/mainwindow.h b/widgets/mainwindow.h index 3243e1e3f..34f516630 100644 --- a/widgets/mainwindow.h +++ b/widgets/mainwindow.h @@ -565,6 +565,7 @@ private: bool m_bBestSPArmed=false; bool m_bOK_to_chk=false; bool m_bSentReport=false; + bool m_discard_decoded_hounds_this_cycle=false; // if something changes, like frequency, discard decoded messages that may be in-flight. SpecOp m_specOp; From eebababece37c94a90932d7e65f945b24d123af7 Mon Sep 17 00:00:00 2001 From: Brian Moran <brian@trucentive.com> Date: Thu, 9 Mar 2023 12:10:20 -0800 Subject: [PATCH 04/14] initial commit --- Network/FileDownload.cpp | 127 +++++++++++++++++++++++++++++++++++++++ Network/FileDownload.hpp | 39 ++++++++++++ Network/LotWUsers.cpp | 5 +- 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 Network/FileDownload.cpp create mode 100644 Network/FileDownload.hpp diff --git a/Network/FileDownload.cpp b/Network/FileDownload.cpp new file mode 100644 index 000000000..63f51d98b --- /dev/null +++ b/Network/FileDownload.cpp @@ -0,0 +1,127 @@ + +#include "FileDownload.hpp" +#include <QCoreApplication> +#include <QUrl> +#include <QNetworkRequest> +#include <QFileInfo> +#include <QDir> +#include <QTemporaryFile> +#include "qt_helpers.hpp" +#include "Logger.hpp" + +FileDownload::FileDownload() : QObject(nullptr) +{ + +} + +FileDownload::~FileDownload() +{ +} + +void FileDownload::errorOccurred(QNetworkReply::NetworkError code) +{ + LOG_INFO(QString{"DOWNLOAD: errorOccurred %1 -> %2"}.arg(code).arg(reply_->errorString())); + //LOG_INFO(QString{ "DOWNLOAD: server returned %1"}.arg(reply_->)) +} + +void FileDownload::configure(const QString &source_url, const QString &destination_path) +{ + source_url_ = source_url; + destination_filename_ = destination_path; +} + +void FileDownload::store() +{ + if (tmpfile_->isOpen()) + tmpfile_->write (reply_->read (reply_->bytesAvailable ())); + else + LOG_INFO(QString{ "DOWNLOAD: tmpfile is not open"}); +} + +void FileDownload::replyComplete() +{ + auto is_error = reply_->error (); + LOG_INFO(QString{"DOWNLOAD: reply complete %1"}.arg(is_error)); + if (reply_ && reply_->isFinished ()) + { + reply_->deleteLater (); + } +} + +void FileDownload::downloadComplete(QNetworkReply *data) +{ + // make a temp file in the same place as the file we're downloading. Needs to be on the same + // filesystem as where we eventually want to 'mv' it. + + QUrl r = request_->url(); + LOG_INFO(QString{"DOWNLOAD: finished download %1 -> %2 (%3)"}.arg(source_url_).arg(destination_filename_).arg(r.url())); + + LOG_INFO(QString{ "DOWNLOAD: tempfile path is %1"}.arg(tmpfile_->fileName())); + + tmpfile_->close(); + + LOG_INFO(QString{"DOWNLOAD: moving file to %2"}.arg(destination_filename_)); + + LOG_INFO("Request Headers:"); + Q_FOREACH (const QByteArray& hdr, request_->rawHeaderList()) { + LOG_INFO(QString{ "%1 -> %2"}.arg(QString(hdr)).arg(QString(request_->rawHeader(hdr)))); + } + + LOG_INFO("Response Headers:"); + Q_FOREACH (const QByteArray& hdr, reply_->rawHeaderList()) { + LOG_INFO(QString{ "%1 -> %2"}.arg(QString(hdr)).arg(QString(reply_->rawHeader(hdr)))); + } + // move the file to the destination + tmpdir_->remove(destination_filename_+".old"); // get rid of previous version + tmpdir_->rename(destination_filename_, destination_filename_+".old"); + tmpdir_->rename(tmpfile_->fileName(), destination_filename_); + emit complete(destination_filename_); + data->deleteLater(); +} + +void FileDownload::download() +{ + //QUrl url = QUrl(source_url_); + + manager_ = new QNetworkAccessManager(this); + + // request_ = new QNetworkRequest("https://www.country-files.com/bigcty/cty.dat"); + request_ = new QNetworkRequest(QUrl(source_url_)); + + LOG_INFO(QString{"DOWNLOAD: starting download %1 -> %2"}.arg(source_url_).arg(destination_filename_)); + + request_->setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + //request_->setHeader( QNetworkRequest::ContentTypeHeader, "some/type" ); + request_->setRawHeader("Accept", "*/*"); + request_->setRawHeader ("User-Agent", "WSJT-X CTY Downloader"); + + reply_ = manager_->get(*request_); + + reply_->setReadBufferSize(0); + + int http_code = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + QObject::connect(manager_, &QNetworkAccessManager::finished, this, &FileDownload::downloadComplete); + QObject::connect(reply_, &QNetworkReply::downloadProgress, this, &FileDownload::downloadProgress); + QObject::connect(reply_, &QNetworkReply::finished, this,&FileDownload::replyComplete); + QObject::connect(reply_, &QNetworkReply::errorOccurred,this,&FileDownload::errorOccurred); + QObject::connect (reply_, &QNetworkReply::finished, this, &FileDownload::replyComplete); + QObject::connect (reply_, &QNetworkReply::readyRead, this, &FileDownload::store); + + QFileInfo tmpfi(destination_filename_); + QString const tmpfile_path = tmpfi.absolutePath(); + tmpdir_ = new QDir(tmpfile_path); + tmpfile_ = new QTemporaryFile(tmpfile_path+"/big.cty.XXXXXX"); + if (!tmpfile_->open()) + { + LOG_INFO(QString{"DOWNLOAD: Unable to open the temporary file based on %1"}.arg(tmpfile_path)); + return; + } + LOG_INFO(QString{"DOWNLOAD: let's go %1"}.arg(http_code)); +} + +void FileDownload::downloadProgress(qint64 received, qint64 total) +{ + LOG_INFO(QString{"DOWNLOAD: Progress %1 from %2, total %3, so far %4"}.arg(destination_filename_).arg(source_url_).arg(total).arg(received)); + //qDebug() << received << total; +} diff --git a/Network/FileDownload.hpp b/Network/FileDownload.hpp new file mode 100644 index 000000000..91409e8fc --- /dev/null +++ b/Network/FileDownload.hpp @@ -0,0 +1,39 @@ +#ifndef WSJTX_FILEDOWNLOAD_H +#define WSJTX_FILEDOWNLOAD_H + +#include <QObject> +#include <QString> +#include <QtNetwork/QNetworkAccessManager> +#include <QtNetwork/QNetworkReply> +#include <QTemporaryFile> + +class FileDownload : public QObject { + Q_OBJECT + +public: + explicit FileDownload(); + ~FileDownload(); + + void configure(const QString& source_url, const QString& destination_filename); + +private: + QNetworkAccessManager *manager_; + QString source_url_; + QString destination_filename_; + QNetworkReply *reply_; + QNetworkRequest *request_; + QTemporaryFile *tmpfile_; + QDir *tmpdir_; +signals: + void complete(QString filename); + +public slots: + void download(); + void store(); + void downloadComplete(QNetworkReply* data); + void downloadProgress(qint64 recieved, qint64 total); + void errorOccurred(QNetworkReply::NetworkError code); + void replyComplete(); +}; + +#endif //WSJTX_FILEDOWNLOAD_H diff --git a/Network/LotWUsers.cpp b/Network/LotWUsers.cpp index 4e8024010..189b0ca9d 100644 --- a/Network/LotWUsers.cpp +++ b/Network/LotWUsers.cpp @@ -16,6 +16,8 @@ #include <QNetworkAccessManager> #include <QNetworkReply> #include <QDebug> +#include "qt_helpers.hpp" +#include "Logger.hpp" #include "pimpl_impl.hpp" @@ -76,7 +78,7 @@ public: network_manager_->setNetworkAccessible (QNetworkAccessManager::Accessible); } #endif - + LOG_INFO(QString("Download...")); QNetworkRequest request {url}; request.setRawHeader ("User-Agent", "WSJT LotW User Downloader"); request.setOriginatingObject (this); @@ -98,6 +100,7 @@ public: void reply_finished () { + LOG_INFO(QString("Finished...")); if (!reply_) { Q_EMIT self_->load_finished (); From b812ac27867ce428516cc8fe7f1346ee59f086c3 Mon Sep 17 00:00:00 2001 From: Brian Moran <brian@trucentive.com> Date: Wed, 15 Mar 2023 20:42:03 -0700 Subject: [PATCH 05/14] show status of LotW file download; new button to download of CTY.DAT, show version of CTY.DAT; genericize download file code; --- CMakeLists.txt | 1 + Configuration.cpp | 51 +++++++++- Configuration.hpp | 2 + Configuration.ui | 129 ++++++++++++++++++------ Network/FileDownload.cpp | 189 ++++++++++++++++++++++++++---------- Network/FileDownload.hpp | 22 +++-- Network/LotWUsers.cpp | 182 ++++++++-------------------------- Network/LotWUsers.hpp | 1 + logbook/AD1CCty.cpp | 151 ++++++++++++++++------------ logbook/AD1CCty.hpp | 3 + logbook/WorkedBefore.cpp | 16 ++- logbook/WorkedBefore.hpp | 3 +- logbook/logbook.cpp | 5 + logbook/logbook.h | 4 +- translations/wsjtx_ca.ts | 2 +- translations/wsjtx_da.ts | 2 +- translations/wsjtx_en.ts | 2 +- translations/wsjtx_en_GB.ts | 2 +- translations/wsjtx_es.ts | 2 +- translations/wsjtx_it.ts | 2 +- translations/wsjtx_ja.ts | 2 +- translations/wsjtx_ru.ts | 2 +- translations/wsjtx_zh.ts | 2 +- translations/wsjtx_zh_HK.ts | 2 +- translations/wsjtx_zh_TW.ts | 2 +- widgets/mainwindow.cpp | 5 +- 26 files changed, 471 insertions(+), 315 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 86bc970c6..e95a92551 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,6 +222,7 @@ set (wsjt_qt_CXXSRCS widgets/DoubleClickablePushButton.cpp widgets/DoubleClickableRadioButton.cpp Network/LotWUsers.cpp + Network/FileDownload.cpp models/DecodeHighlightingModel.cpp widgets/DecodeHighlightingListView.cpp models/FoxLog.cpp diff --git a/Configuration.cpp b/Configuration.cpp index c8652a599..aed2c5417 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -201,6 +201,7 @@ #include "models/DecodeHighlightingModel.hpp" #include "logbook/logbook.h" #include "widgets/LazyFillComboBox.hpp" +#include "Network/FileDownload.hpp" #include "ui_Configuration.h" #include "moc_Configuration.cpp" @@ -564,6 +565,8 @@ private: Q_SLOT void on_add_macro_line_edit_editingFinished (); Q_SLOT void delete_macro (); void delete_selected_macros (QModelIndexList); + void after_CTY_downloaded(); + void set_CTY_DAT_version(QString const& version); Q_SLOT void on_udp_server_line_edit_textChanged (QString const&); Q_SLOT void on_udp_server_line_edit_editingFinished (); Q_SLOT void on_save_path_select_push_button_clicked (bool); @@ -574,7 +577,9 @@ private: Q_SLOT void handle_transceiver_failure (QString const& reason); Q_SLOT void on_reset_highlighting_to_defaults_push_button_clicked (bool); Q_SLOT void on_rescan_log_push_button_clicked (bool); + Q_SLOT void on_CTY_download_button_clicked (bool); Q_SLOT void on_LotW_CSV_fetch_push_button_clicked (bool); + Q_SLOT void on_cbx2ToneSpacing_clicked(bool); Q_SLOT void on_cbx4ToneSpacing_clicked(bool); Q_SLOT void on_prompt_to_log_check_box_clicked(bool); @@ -746,7 +751,7 @@ private: QAudioDeviceInfo next_audio_output_device_; AudioDevice::Channel audio_output_channel_; AudioDevice::Channel next_audio_output_channel_; - + FileDownload cty_download; friend class Configuration; }; @@ -859,6 +864,11 @@ bool Configuration::highlight_73 () const {return m_->highlight_73_;} bool Configuration::highlight_DXcall () const {return m_->highlight_DXcall_;} bool Configuration::highlight_DXgrid () const {return m_->highlight_DXgrid_;} +void Configuration::set_CTY_DAT_version(QString const& version) +{ + m_->set_CTY_DAT_version(version); +} + void Configuration::set_calibration (CalibrationParams params) { m_->calibration_ = params; @@ -1174,8 +1184,13 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network // set up LoTW users CSV file fetching connect (&lotw_users_, &LotWUsers::load_finished, [this] () { - ui_->LotW_CSV_fetch_push_button->setEnabled (true); - }); + ui_->LotW_CSV_fetch_push_button->setEnabled (true); + }); + + connect(&lotw_users_, &LotWUsers::progress, [this] (QString const& msg) { + ui_->LotW_CSV_status_label->setText(msg); + }); + lotw_users_.set_local_file_path (writeable_data_dir_.absoluteFilePath ("lotw-user-activity.csv")); // @@ -2402,9 +2417,37 @@ void Configuration::impl::on_reset_highlighting_to_defaults_push_button_clicked void Configuration::impl::on_rescan_log_push_button_clicked (bool /*clicked*/) { - if (logbook_) logbook_->rescan (); + if (logbook_) { + logbook_->rescan (); + } } +void Configuration::impl::on_CTY_download_button_clicked (bool /*clicked*/) +{ + ui_->CTY_download_button->setEnabled (false); // disable button until download is complete + QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::AppDataLocation)}; + cty_download.configure(network_manager_, + "http://www.country-files.com/bigcty/cty.dat", + dataPath.absoluteFilePath("cty.dat"), + "WSJT-X CTY Downloader"); + + // set up LoTW users CSV file fetching + connect (&cty_download, &FileDownload::complete, this, &Configuration::impl::after_CTY_downloaded, Qt::UniqueConnection); + cty_download.start_download(); +} +void Configuration::impl::set_CTY_DAT_version(QString const& version) +{ + ui_->CTY_file_label->setText(QString{"CTY File Version: %1"}.arg(version)); +} + +void Configuration::impl::after_CTY_downloaded () +{ + ui_->CTY_download_button->setEnabled (true); + if (logbook_) { + logbook_->rescan (); + ui_->CTY_file_label->setText(QString{"CTY File Version: %1"}.arg(logbook_->cty_version())); + } +} void Configuration::impl::on_LotW_CSV_fetch_push_button_clicked (bool /*checked*/) { lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), true, true); diff --git a/Configuration.hpp b/Configuration.hpp index dbf6b9124..c3e24d935 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -243,6 +243,8 @@ public: // Close down connection to rig. void transceiver_offline (); + void set_CTY_DAT_version(QString const& version); + // Set transceiver frequency in Hertz. Q_SLOT void transceiver_frequency (Frequency); diff --git a/Configuration.ui b/Configuration.ui index 1f91e09cd..e7502c617 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>588</width> - <height>642</height> + <width>684</width> + <height>662</height> </rect> </property> <property name="windowTitle"> @@ -2407,8 +2407,14 @@ Right click for insert and delete options.</string> <property name="title"> <string>Logbook of the World User Validation</string> </property> - <layout class="QFormLayout" name="formLayout_18"> - <item row="1" column="0"> + <layout class="QGridLayout" name="gridLayout_13"> + <property name="topMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item row="0" column="0"> <widget class="QLabel" name="label_15"> <property name="text"> <string>Users CSV file URL:</string> @@ -2418,7 +2424,7 @@ Right click for insert and delete options.</string> </property> </widget> </item> - <item row="1" column="1"> + <item row="0" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_20"> <item> <widget class="QLineEdit" name="LotW_CSV_URL_line_edit"> @@ -2445,7 +2451,7 @@ Right click for insert and delete options.</string> </item> </layout> </item> - <item row="2" column="0"> + <item row="1" column="0"> <widget class="QLabel" name="label_14"> <property name="text"> <string>Age of last upload less than:</string> @@ -2455,27 +2461,57 @@ Right click for insert and delete options.</string> </property> </widget> </item> - <item row="2" column="1"> - <widget class="QSpinBox" name="LotW_days_since_upload_spin_box"> - <property name="toolTip"> - <string><html><head/><body><p>Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.</p></body></html></string> - </property> - <property name="accessibleName"> - <string>Days since last upload</string> - </property> - <property name="suffix"> - <string> days</string> - </property> - <property name="minimum"> - <number>0</number> - </property> - <property name="maximum"> - <number>9999</number> - </property> - <property name="value"> - <number>365</number> - </property> - </widget> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_23"> + <item> + <widget class="QSpinBox" name="LotW_days_since_upload_spin_box"> + <property name="toolTip"> + <string><html><head/><body><p>Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.</p></body></html></string> + </property> + <property name="accessibleName"> + <string>Days since last upload</string> + </property> + <property name="suffix"> + <string> days</string> + </property> + <property name="minimum"> + <number>0</number> + </property> + <property name="maximum"> + <number>9999</number> + </property> + <property name="value"> + <number>365</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_13"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="LotW_CSV_status_label"> + <property name="minimumSize"> + <size> + <width>240</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> </item> </layout> </widget> @@ -2493,6 +2529,35 @@ Right click for insert and delete options.</string> </property> </spacer> </item> + <item> + <widget class="QGroupBox" name="groupBox_8"> + <property name="title"> + <string>CTY File Download</string> + </property> + <layout class="QGridLayout" name="gridLayout_17"> + <property name="topMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="CTY_file_label"> + <property name="text"> + <string>CTY File Version: </string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QPushButton" name="CTY_download_button"> + <property name="text"> + <string>Download Latest CTY.dat</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> <item> <spacer name="verticalSpacer_6"> <property name="orientation"> @@ -2501,7 +2566,7 @@ Right click for insert and delete options.</string> <property name="sizeHint" stdset="0"> <size> <width>20</width> - <height>40</height> + <height>20</height> </size> </property> </spacer> @@ -3336,13 +3401,13 @@ Right click for insert and delete options.</string> </connection> </connections> <buttongroups> - <buttongroup name="CAT_stop_bits_button_group"/> - <buttongroup name="CAT_handshake_button_group"/> - <buttongroup name="TX_mode_button_group"/> <buttongroup name="CAT_data_bits_button_group"/> <buttongroup name="PTT_method_button_group"/> - <buttongroup name="split_mode_button_group"/> + <buttongroup name="CAT_stop_bits_button_group"/> <buttongroup name="special_op_activity_button_group"/> + <buttongroup name="split_mode_button_group"/> + <buttongroup name="TX_mode_button_group"/> + <buttongroup name="CAT_handshake_button_group"/> <buttongroup name="TX_audio_source_button_group"/> </buttongroups> </ui> diff --git a/Network/FileDownload.cpp b/Network/FileDownload.cpp index 63f51d98b..7630ae59b 100644 --- a/Network/FileDownload.cpp +++ b/Network/FileDownload.cpp @@ -3,6 +3,8 @@ #include <QCoreApplication> #include <QUrl> #include <QNetworkRequest> +#include <QtNetwork/QNetworkAccessManager> +#include <QtNetwork/QNetworkReply> #include <QFileInfo> #include <QDir> #include <QTemporaryFile> @@ -11,7 +13,8 @@ FileDownload::FileDownload() : QObject(nullptr) { - + redirect_count_ = 0; + url_valid_ = false; } FileDownload::~FileDownload() @@ -20,14 +23,17 @@ FileDownload::~FileDownload() void FileDownload::errorOccurred(QNetworkReply::NetworkError code) { - LOG_INFO(QString{"DOWNLOAD: errorOccurred %1 -> %2"}.arg(code).arg(reply_->errorString())); - //LOG_INFO(QString{ "DOWNLOAD: server returned %1"}.arg(reply_->)) + LOG_INFO(QString{"FileDownload [%1]: errorOccurred %2 -> %3"}.arg(user_agent_).arg(code).arg(reply_->errorString())); + Q_EMIT error (reply_->errorString ()); + delete tmpfile_; } -void FileDownload::configure(const QString &source_url, const QString &destination_path) +void FileDownload::configure(QNetworkAccessManager *network_manager, const QString &source_url, const QString &destination_path, const QString &user_agent) { + manager_ = network_manager; source_url_ = source_url; destination_filename_ = destination_path; + user_agent_ = user_agent; } void FileDownload::store() @@ -35,17 +41,89 @@ void FileDownload::store() if (tmpfile_->isOpen()) tmpfile_->write (reply_->read (reply_->bytesAvailable ())); else - LOG_INFO(QString{ "DOWNLOAD: tmpfile is not open"}); + LOG_INFO(QString{ "FileDownload [%1]: tmpfile is not open"}.arg(user_agent_)); } void FileDownload::replyComplete() { - auto is_error = reply_->error (); - LOG_INFO(QString{"DOWNLOAD: reply complete %1"}.arg(is_error)); + QFileInfo destination_file(destination_filename_); + QString const tmpfile_path = destination_file.absolutePath(); + QDir tmpdir_(tmpfile_path); + + LOG_DEBUG(QString{ "FileDownload [%1]: replyComplete"}.arg(user_agent_)); + if (!reply_) + { + Q_EMIT load_finished (); + return; // we probably deleted it in an earlier call + } + + QUrl redirect_url {reply_->attribute (QNetworkRequest::RedirectionTargetAttribute).toUrl ()}; + + if (reply_->error () == QNetworkReply::NoError && !redirect_url.isEmpty ()) + { + if ("https" == redirect_url.scheme () && !QSslSocket::supportsSsl ()) + { + Q_EMIT download_error (tr ("Network Error - SSL/TLS support not installed, cannot fetch:\n\'%1\'") + .arg (redirect_url.toDisplayString ())); + url_valid_ = false; // reset + Q_EMIT load_finished (); + } + else if (++redirect_count_ < 10) // maintain sanity + { + // follow redirect + download (reply_->url ().resolved (redirect_url)); + } + else + { + Q_EMIT download_error (tr ("Network Error - Too many redirects:\n\'%1\'") + .arg (redirect_url.toDisplayString ())); + url_valid_ = false; // reset + Q_EMIT load_finished (); + } + } + else if (reply_->error () != QNetworkReply::NoError) + { + tmpfile_->close(); + delete tmpfile_; + url_valid_ = false; // reset + // report errors that are not due to abort + if (QNetworkReply::OperationCanceledError != reply_->error ()) + { + Q_EMIT download_error (tr ("Network Error:\n%1") + .arg (reply_->errorString ())); + } + Q_EMIT load_finished (); + } + else + { + if (!url_valid_) + { + // now get the body content + url_valid_ = true; + download (reply_->url ().resolved (redirect_url)); + } + else // the body has completed. Save it. + { + url_valid_ = false; // reset + // load the database asynchronously + // future_load_ = std::async (std::launch::async, &LotWUsers::impl::load_dictionary, this, csv_file_.fileName ()); + LOG_INFO(QString{ "FileDownload [%1]: complete. tempfile path is %2"}.arg(user_agent_).arg(tmpfile_->fileName())); + // move the file to the destination + tmpdir_.remove(destination_filename_+".old"); // get rid of previous version + tmpdir_.rename(destination_filename_, destination_filename_+".old"); + tmpdir_.rename(tmpfile_->fileName(), destination_filename_); + LOG_INFO(QString{ "FileDownload [%1]: moved tempfile %2 to %3"}.arg(user_agent_).arg(tmpfile_->fileName()).arg(destination_filename_)); + tmpfile_->close(); + delete tmpfile_; + emit complete(destination_filename_); + } + } + if (reply_ && reply_->isFinished ()) { reply_->deleteLater (); } + } void FileDownload::downloadComplete(QNetworkReply *data) @@ -53,75 +131,84 @@ void FileDownload::downloadComplete(QNetworkReply *data) // make a temp file in the same place as the file we're downloading. Needs to be on the same // filesystem as where we eventually want to 'mv' it. - QUrl r = request_->url(); - LOG_INFO(QString{"DOWNLOAD: finished download %1 -> %2 (%3)"}.arg(source_url_).arg(destination_filename_).arg(r.url())); - - LOG_INFO(QString{ "DOWNLOAD: tempfile path is %1"}.arg(tmpfile_->fileName())); - - tmpfile_->close(); - - LOG_INFO(QString{"DOWNLOAD: moving file to %2"}.arg(destination_filename_)); + QUrl r = request_.url(); + LOG_INFO(QString{"FileDownload [%1]: finished %2 of %3 -> %4 (%5)"}.arg(user_agent_).arg(data->operation()).arg(source_url_).arg(destination_filename_).arg(r.url())); +#ifdef DEBUG_FILEDOWNLOAD LOG_INFO("Request Headers:"); - Q_FOREACH (const QByteArray& hdr, request_->rawHeaderList()) { - LOG_INFO(QString{ "%1 -> %2"}.arg(QString(hdr)).arg(QString(request_->rawHeader(hdr)))); - } + Q_FOREACH (const QByteArray& hdr, request_.rawHeaderList()) { + LOG_INFO(QString{ "%1 -> %2"}.arg(QString(hdr)).arg(QString(request_.rawHeader(hdr)))); + } LOG_INFO("Response Headers:"); Q_FOREACH (const QByteArray& hdr, reply_->rawHeaderList()) { LOG_INFO(QString{ "%1 -> %2"}.arg(QString(hdr)).arg(QString(reply_->rawHeader(hdr)))); } - // move the file to the destination - tmpdir_->remove(destination_filename_+".old"); // get rid of previous version - tmpdir_->rename(destination_filename_, destination_filename_+".old"); - tmpdir_->rename(tmpfile_->fileName(), destination_filename_); - emit complete(destination_filename_); +#endif data->deleteLater(); } -void FileDownload::download() +void FileDownload::start_download() { - //QUrl url = QUrl(source_url_); + url_valid_ = false; + download(QUrl(source_url_)); +} - manager_ = new QNetworkAccessManager(this); +void FileDownload::download(QUrl qurl) +{ + request_.setUrl(qurl); - // request_ = new QNetworkRequest("https://www.country-files.com/bigcty/cty.dat"); - request_ = new QNetworkRequest(QUrl(source_url_)); +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + if (QNetworkAccessManager::Accessible != manager_->networkAccessible ()) + { + // try and recover network access for QNAM + manager_->setNetworkAccessible (QNetworkAccessManager::Accessible); + } +#endif - LOG_INFO(QString{"DOWNLOAD: starting download %1 -> %2"}.arg(source_url_).arg(destination_filename_)); + LOG_INFO(QString{"FileDownload [%1]: Starting download of %2 to %3"}.arg(user_agent_).arg(source_url_).arg(destination_filename_)); - request_->setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - //request_->setHeader( QNetworkRequest::ContentTypeHeader, "some/type" ); - request_->setRawHeader("Accept", "*/*"); - request_->setRawHeader ("User-Agent", "WSJT-X CTY Downloader"); + request_.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + request_.setRawHeader("Accept", "*/*"); + request_.setRawHeader ("User-Agent", user_agent_.toLocal8Bit()); // Must have a UA for some sites, like country-files - reply_ = manager_->get(*request_); + if (!url_valid_) + { + reply_ = manager_->head(request_); + } + else + { + reply_ = manager_->get (request_); + } - reply_->setReadBufferSize(0); + QObject::connect(manager_, &QNetworkAccessManager::finished, this, &FileDownload::downloadComplete, Qt::UniqueConnection); + QObject::connect(reply_, &QNetworkReply::downloadProgress, this, &FileDownload::downloadProgress, Qt::UniqueConnection); + QObject::connect(reply_, &QNetworkReply::finished, this,&FileDownload::replyComplete, Qt::UniqueConnection); - int http_code = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QObject::connect(reply_, &QNetworkReply::errorOccurred,this,&FileDownload::errorOccurred, Qt::UniqueConnection); + QObject::connect (reply_, &QNetworkReply::readyRead, this, &FileDownload::store, Qt::UniqueConnection); - QObject::connect(manager_, &QNetworkAccessManager::finished, this, &FileDownload::downloadComplete); - QObject::connect(reply_, &QNetworkReply::downloadProgress, this, &FileDownload::downloadProgress); - QObject::connect(reply_, &QNetworkReply::finished, this,&FileDownload::replyComplete); - QObject::connect(reply_, &QNetworkReply::errorOccurred,this,&FileDownload::errorOccurred); - QObject::connect (reply_, &QNetworkReply::finished, this, &FileDownload::replyComplete); - QObject::connect (reply_, &QNetworkReply::readyRead, this, &FileDownload::store); - - QFileInfo tmpfi(destination_filename_); - QString const tmpfile_path = tmpfi.absolutePath(); - tmpdir_ = new QDir(tmpfile_path); - tmpfile_ = new QTemporaryFile(tmpfile_path+"/big.cty.XXXXXX"); + QFileInfo destination_file(destination_filename_); + QString const tmpfile_base = destination_file.fileName(); + QString const tmpfile_path = destination_file.absolutePath(); + tmpfile_ = new QTemporaryFile(tmpfile_path+QDir::separator()+tmpfile_base+".XXXXXX"); if (!tmpfile_->open()) { - LOG_INFO(QString{"DOWNLOAD: Unable to open the temporary file based on %1"}.arg(tmpfile_path)); + LOG_INFO(QString{"FileDownload [%1]: Unable to open the temporary file based on %2"}.arg(user_agent_).arg(tmpfile_path)); return; } - LOG_INFO(QString{"DOWNLOAD: let's go %1"}.arg(http_code)); } void FileDownload::downloadProgress(qint64 received, qint64 total) { - LOG_INFO(QString{"DOWNLOAD: Progress %1 from %2, total %3, so far %4"}.arg(destination_filename_).arg(source_url_).arg(total).arg(received)); - //qDebug() << received << total; + LOG_DEBUG(QString{"FileDownload: [%1] Progress %2 from %3, total %4, so far %5"}.arg(user_agent_).arg(destination_filename_).arg(source_url_).arg(total).arg(received)); + Q_EMIT progress(QString{"%4 bytes downloaded"}.arg(received)); } + +void FileDownload::abort () +{ + if (reply_ && reply_->isRunning ()) + { + reply_->abort (); + } +} \ No newline at end of file diff --git a/Network/FileDownload.hpp b/Network/FileDownload.hpp index 91409e8fc..b562cc925 100644 --- a/Network/FileDownload.hpp +++ b/Network/FileDownload.hpp @@ -3,6 +3,7 @@ #include <QObject> #include <QString> +#include <QPointer> #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> #include <QTemporaryFile> @@ -14,22 +15,31 @@ public: explicit FileDownload(); ~FileDownload(); - void configure(const QString& source_url, const QString& destination_filename); + void configure(QNetworkAccessManager *network_manager, const QString& source_url, const QString& destination_filename, const QString& user_agent); private: QNetworkAccessManager *manager_; QString source_url_; QString destination_filename_; - QNetworkReply *reply_; - QNetworkRequest *request_; - QTemporaryFile *tmpfile_; - QDir *tmpdir_; + QString user_agent_; + QPointer<QNetworkReply> reply_; + QNetworkRequest request_; + QPointer<QTemporaryFile> tmpfile_; + bool url_valid_; + int redirect_count_; signals: void complete(QString filename); + void progress(QString filename); + void load_finished() const; + void download_error (QString const& reason) const; + void error(QString const& reason) const; + public slots: - void download(); + void start_download(); + void download(QUrl url); void store(); + void abort(); void downloadComplete(QNetworkReply* data); void downloadProgress(qint64 recieved, qint64 total); void errorOccurred(QNetworkReply::NetworkError code); diff --git a/Network/LotWUsers.cpp b/Network/LotWUsers.cpp index 189b0ca9d..10a9a4d8b 100644 --- a/Network/LotWUsers.cpp +++ b/Network/LotWUsers.cpp @@ -18,7 +18,7 @@ #include <QDebug> #include "qt_helpers.hpp" #include "Logger.hpp" - +#include "FileDownload.hpp" #include "pimpl_impl.hpp" #include "moc_LotWUsers.cpp" @@ -41,6 +41,7 @@ public: , url_valid_ {false} , redirect_count_ {0} , age_constraint_ {365} + , connected_ {false} { } @@ -50,14 +51,36 @@ public: auto csv_file_name = csv_file_.fileName (); auto exists = QFileInfo::exists (csv_file_name); if (fetch && (!exists || forced_fetch)) + { + current_url_.setUrl(url); + if (current_url_.isValid() && !QSslSocket::supportsSsl()) { - current_url_.setUrl (url); - if (current_url_.isValid () && !QSslSocket::supportsSsl ()) - { - current_url_.setScheme ("http"); - } - redirect_count_ = 0; - download (current_url_); + current_url_.setScheme("http"); + } + redirect_count_ = 0; + + Q_EMIT self_->progress (QString("Starting download from %1").arg(url)); + + lotw_downloader_.configure(network_manager_, + url, + csv_file_name, + "WSJT-X LotW User Downloader"); + if (!connected_) + { + connect(&lotw_downloader_, &FileDownload::complete, [this, csv_file_name] { + LOG_INFO(QString{"LotWUsers: Loading LotW file %1"}.arg(csv_file_name)); + future_load_ = std::async(std::launch::async, &LotWUsers::impl::load_dictionary, this, csv_file_name); + }); + connect(&lotw_downloader_, &FileDownload::error, [this] (QString const& msg) { + LOG_INFO(QString{"LotWUsers: Error downloading LotW file: %1"}.arg(msg)); + Q_EMIT self_->LotW_users_error (msg); + }); + connect( &lotw_downloader_, &FileDownload::progress, [this] (QString const& msg) { + Q_EMIT self_->progress (msg); + }); + connected_ = true; + } + lotw_downloader_.start_download(); } else { @@ -69,143 +92,9 @@ public: } } - void download (QUrl url) - { -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - if (QNetworkAccessManager::Accessible != network_manager_->networkAccessible ()) - { - // try and recover network access for QNAM - network_manager_->setNetworkAccessible (QNetworkAccessManager::Accessible); - } -#endif - LOG_INFO(QString("Download...")); - QNetworkRequest request {url}; - request.setRawHeader ("User-Agent", "WSJT LotW User Downloader"); - request.setOriginatingObject (this); - - // this blocks for a second or two the first time it is used on - // Windows - annoying - if (!url_valid_) - { - reply_ = network_manager_->head (request); - } - else - { - reply_ = network_manager_->get (request); - } - - connect (reply_.data (), &QNetworkReply::finished, this, &LotWUsers::impl::reply_finished); - connect (reply_.data (), &QNetworkReply::readyRead, this, &LotWUsers::impl::store); - } - - void reply_finished () - { - LOG_INFO(QString("Finished...")); - if (!reply_) - { - Q_EMIT self_->load_finished (); - return; // we probably deleted it in an earlier call - } - QUrl redirect_url {reply_->attribute (QNetworkRequest::RedirectionTargetAttribute).toUrl ()}; - if (reply_->error () == QNetworkReply::NoError && !redirect_url.isEmpty ()) - { - if ("https" == redirect_url.scheme () && !QSslSocket::supportsSsl ()) - { - Q_EMIT self_->LotW_users_error (tr ("Network Error - SSL/TLS support not installed, cannot fetch:\n\'%1\'") - .arg (redirect_url.toDisplayString ())); - url_valid_ = false; // reset - Q_EMIT self_->load_finished (); - } - else if (++redirect_count_ < 10) // maintain sanity - { - // follow redirect - download (reply_->url ().resolved (redirect_url)); - } - else - { - Q_EMIT self_->LotW_users_error (tr ("Network Error - Too many redirects:\n\'%1\'") - .arg (redirect_url.toDisplayString ())); - url_valid_ = false; // reset - Q_EMIT self_->load_finished (); - } - } - else if (reply_->error () != QNetworkReply::NoError) - { - csv_file_.cancelWriting (); - csv_file_.commit (); - url_valid_ = false; // reset - // report errors that are not due to abort - if (QNetworkReply::OperationCanceledError != reply_->error ()) - { - Q_EMIT self_->LotW_users_error (tr ("Network Error:\n%1") - .arg (reply_->errorString ())); - } - Q_EMIT self_->load_finished (); - } - else - { - if (url_valid_ && !csv_file_.commit ()) - { - Q_EMIT self_->LotW_users_error (tr ("File System Error - Cannot commit changes to:\n\"%1\"") - .arg (csv_file_.fileName ())); - url_valid_ = false; // reset - Q_EMIT self_->load_finished (); - } - else - { - if (!url_valid_) - { - // now get the body content - url_valid_ = true; - download (reply_->url ().resolved (redirect_url)); - } - else - { - url_valid_ = false; // reset - // load the database asynchronously - future_load_ = std::async (std::launch::async, &LotWUsers::impl::load_dictionary, this, csv_file_.fileName ()); - } - } - } - if (reply_ && reply_->isFinished ()) - { - reply_->deleteLater (); - } - } - - void store () - { - if (url_valid_) - { - if (!csv_file_.isOpen ()) - { - // create temporary file in the final location - if (!csv_file_.open (QSaveFile::WriteOnly)) - { - abort (); - Q_EMIT self_->LotW_users_error (tr ("File System Error - Cannot open file:\n\"%1\"\nError(%2): %3") - .arg (csv_file_.fileName ()) - .arg (csv_file_.error ()) - .arg (csv_file_.errorString ())); - } - } - if (csv_file_.write (reply_->read (reply_->bytesAvailable ())) < 0) - { - abort (); - Q_EMIT self_->LotW_users_error (tr ("File System Error - Cannot write to file:\n\"%1\"\nError(%2): %3") - .arg (csv_file_.fileName ()) - .arg (csv_file_.error ()) - .arg (csv_file_.errorString ())); - } - } - } - void abort () { - if (reply_ && reply_->isRunning ()) - { - reply_->abort (); - } + lotw_downloader_.abort(); } // Load the database from the given file name @@ -225,12 +114,14 @@ public: auto pos = l.indexOf (','); result[l.left (pos)] = QDate::fromString (l.mid (pos + 1, l.indexOf (',', pos + 1) - pos - 1), "yyyy-MM-dd"); } -// qDebug () << "LotW User Data Loaded"; } else { throw std::runtime_error {QObject::tr ("Failed to open LotW users CSV file: '%1'").arg (f.fileName ()).toStdString ()}; } + LOG_INFO(QString{"LotWUsers: Loaded %1 records from %2"}.arg(result.size()).arg(lotw_csv_file)); + Q_EMIT self_->progress (QString{"Loaded %1 records from LotW."}.arg(result.size())); + Q_EMIT self_->load_finished(); return result; } @@ -244,6 +135,8 @@ public: std::future<dictionary> future_load_; dictionary last_uploaded_; qint64 age_constraint_; // days + FileDownload lotw_downloader_; + bool connected_; }; #include "LotWUsers.moc" @@ -252,6 +145,7 @@ LotWUsers::LotWUsers (QNetworkAccessManager * network_manager, QObject * parent) : QObject {parent} , m_ {this, network_manager} { + } LotWUsers::~LotWUsers () diff --git a/Network/LotWUsers.hpp b/Network/LotWUsers.hpp index 238c57402..2d4d13075 100644 --- a/Network/LotWUsers.hpp +++ b/Network/LotWUsers.hpp @@ -31,6 +31,7 @@ public: bool user (QString const& call) const; Q_SIGNAL void LotW_users_error (QString const& reason) const; + Q_SIGNAL void progress (QString const& reason) const; Q_SIGNAL void load_finished () const; private: diff --git a/logbook/AD1CCty.cpp b/logbook/AD1CCty.cpp index 4112ddfb3..05a285430 100644 --- a/logbook/AD1CCty.cpp +++ b/logbook/AD1CCty.cpp @@ -19,6 +19,7 @@ #include "Configuration.hpp" #include "Radio.hpp" #include "pimpl_impl.hpp" +#include "Logger.hpp" #include "moc_AD1CCty.cpp" @@ -163,6 +164,9 @@ public: { } + QString get_cty_path(const Configuration *configuration); + void load_cty(QFile &file); + entity_by_id::iterator lookup_entity (QString call, prefix const& p) const { call = call.toUpper (); @@ -228,6 +232,7 @@ public: Configuration const * configuration_; QString path_; + QString cty_version_; entities_type entities_; prefixes_type prefixes_; }; @@ -314,6 +319,72 @@ char const * AD1CCty::continent (Continent c) } } +QString AD1CCty::impl::get_cty_path(Configuration const * configuration) +{ + QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::AppDataLocation)}; + auto path = dataPath.exists (file_name) + ? dataPath.absoluteFilePath (file_name) // user override + : configuration->data_dir ().absoluteFilePath (file_name); // or original + return path; +} + +void AD1CCty::impl::load_cty(QFile &file) +{ + int entity_id = 0; + int line_number{0}; + + entities_.clear(); + prefixes_.clear(); + + QTextStream in{&file}; + while (!in.atEnd()) + { + auto const &entity_line = in.readLine(); + ++line_number; + if (!in.atEnd()) + { + auto const &entity_parts = entity_line.split(':'); + if (entity_parts.size() >= 8) + { + auto primary_prefix = entity_parts[7].trimmed(); + bool WAE_only{false}; + if (primary_prefix.startsWith('*')) + { + primary_prefix = primary_prefix.mid(1); + WAE_only = true; + } + bool ok1, ok2, ok3, ok4, ok5; + entities_.emplace(++entity_id, entity_parts[0].trimmed(), WAE_only, entity_parts[1].trimmed().toInt(&ok1), + entity_parts[2].trimmed().toInt(&ok2), continent(entity_parts[3].trimmed()), + entity_parts[4].trimmed().toFloat(&ok3), entity_parts[5].trimmed().toFloat(&ok4), + static_cast<int> (entity_parts[6].trimmed().toFloat(&ok5) * 60 * 60), primary_prefix); + if (!(ok1 && ok2 && ok3 && ok4 && ok5)) + { + throw std::domain_error{"Invalid number in cty.dat line " + boost::lexical_cast<std::string>(line_number)}; + } + QString line; + QString detail; + do + { + in.readLineInto(&line); + ++line_number; + } while (detail += line, !detail.endsWith(';')); + for (auto prefix: detail.left(detail.size() - 1).split(',')) + { + prefix = prefix.trimmed(); + bool exact{false}; + if (prefix.startsWith('=')) + { + prefix = prefix.mid(1); + exact = true; + } + prefixes_.emplace(prefix, exact, entity_id); + } + } + } + } +} + AD1CCty::AD1CCty (Configuration const * configuration) : m_ {configuration} { @@ -321,69 +392,23 @@ AD1CCty::AD1CCty (Configuration const * configuration) // TODO: G4WJS - consider doing the following asynchronously to // speed up startup. Not urgent as it takes less than 0.5s on a Core // i7 reading BIG CTY.DAT. - QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}; - m_->path_ = dataPath.exists (file_name) - ? dataPath.absoluteFilePath (file_name) // user override - : configuration->data_dir ().absoluteFilePath (file_name); // or original + AD1CCty::reload (configuration); + } + +void AD1CCty::reload(Configuration const * configuration) +{ + m_->path_ = m_->impl::get_cty_path(configuration); QFile file {m_->path_}; + + LOG_INFO(QString{"Loading CTY.DAT from %1"}.arg (m_->path_)); + if (file.open (QFile::ReadOnly)) - { - int entity_id = 0; - int line_number {0}; - QTextStream in {&file}; - while (!in.atEnd ()) - { - auto const& entity_line = in.readLine (); - ++line_number; - if (!in.atEnd ()) - { - auto const& entity_parts = entity_line.split (':'); - if (entity_parts.size () >= 8) - { - auto primary_prefix = entity_parts[7].trimmed (); - bool WAE_only {false}; - if (primary_prefix.startsWith ('*')) - { - primary_prefix = primary_prefix.mid (1); - WAE_only = true; - } - bool ok1, ok2, ok3, ok4, ok5; - m_->entities_.emplace (++entity_id - , entity_parts[0].trimmed () - , WAE_only - , entity_parts[1].trimmed ().toInt (&ok1) - , entity_parts[2].trimmed ().toInt (&ok2) - , continent (entity_parts[3].trimmed ()) - , entity_parts[4].trimmed ().toFloat (&ok3) - , entity_parts[5].trimmed ().toFloat (&ok4) - , static_cast<int> (entity_parts[6].trimmed ().toFloat (&ok5) * 60 * 60) - , primary_prefix); - if (!(ok1 && ok2 && ok3 && ok4 && ok5)) - { - throw std::domain_error {"Invalid number in cty.dat line " + boost::lexical_cast<std::string> (line_number)}; - } - QString line; - QString detail; - do - { - in.readLineInto (&line); - ++line_number; - } while (detail += line, !detail.endsWith (';')); - for (auto prefix : detail.left (detail.size () - 1).split (',')) - { - prefix = prefix.trimmed (); - bool exact {false}; - if (prefix.startsWith ('=')) - { - prefix = prefix.mid (1); - exact = true; - } - m_->prefixes_.emplace (prefix, exact, entity_id); - } - } - } - } - } + { + m_->impl::load_cty(file); + m_->cty_version_ = AD1CCty::lookup("VERSION").entity_name; + Q_EMIT cty_loaded(m_->cty_version_); + LOG_INFO(QString{"Loaded CTY.DAT version %1"}.arg (m_->cty_version_)); + } } AD1CCty::~AD1CCty () @@ -421,3 +446,7 @@ auto AD1CCty::lookup (QString const& call) const -> Record } return Record {}; } +auto AD1CCty::version () const -> QString +{ + return m_->cty_version_; +} diff --git a/logbook/AD1CCty.hpp b/logbook/AD1CCty.hpp index 4a485afa9..982ff7cec 100644 --- a/logbook/AD1CCty.hpp +++ b/logbook/AD1CCty.hpp @@ -42,8 +42,11 @@ public: }; explicit AD1CCty (Configuration const *); + void reload(Configuration const * configuration); ~AD1CCty (); Record lookup (QString const& call) const; + QString version () const; + Q_SIGNAL void cty_loaded (QString const& version) const; private: class impl; diff --git a/logbook/WorkedBefore.cpp b/logbook/WorkedBefore.cpp index e7214d41b..b5dedc3d2 100644 --- a/logbook/WorkedBefore.cpp +++ b/logbook/WorkedBefore.cpp @@ -23,6 +23,7 @@ #include <QDateTime> #include "Configuration.hpp" #include "revision_utils.hpp" +#include "Logger.hpp" #include "qt_helpers.hpp" #include "pimpl_impl.hpp" @@ -225,7 +226,7 @@ namespace { auto const logFileName = "wsjtx_log.adi"; - // Expception class suitable for using with QtConcurrent across + // Exception class suitable for using with QtConcurrent across // thread boundaries class LoaderException final : public QException @@ -374,6 +375,7 @@ public: void reload () { + prefixes_.reload (configuration_); async_loader_ = QtConcurrent::run (loader, path_, &prefixes_); loader_watcher_.setFuture (async_loader_); } @@ -402,11 +404,18 @@ WorkedBefore::WorkedBefore (Configuration const * configuration) { error = e.error (); } - Q_EMIT finished_loading (n, error); + QString cty_ver = m_->prefixes_.version(); + LOG_DEBUG(QString{"WorkedBefore::reload: CTY.DAT version %1"}.arg (cty_ver)); + Q_EMIT finished_loading (n, cty_ver, error); }); reload (); } +QString WorkedBefore::cty_version () const +{ + return m_->prefixes_.version (); +} + void WorkedBefore::reload () { m_->reload (); @@ -668,6 +677,7 @@ bool WorkedBefore::CQ_zone_worked (int CQ_zone, QString const& mode, QString con } } + bool WorkedBefore::ITU_zone_worked (int ITU_zone, QString const& mode, QString const& band) const { if (mode.size ()) @@ -699,3 +709,5 @@ bool WorkedBefore::ITU_zone_worked (int ITU_zone, QString const& mode, QString c } } } + + diff --git a/logbook/WorkedBefore.hpp b/logbook/WorkedBefore.hpp index 1aae1aca4..0be9d783f 100644 --- a/logbook/WorkedBefore.hpp +++ b/logbook/WorkedBefore.hpp @@ -36,8 +36,9 @@ public: bool continent_worked (Continent continent, QString const& mode, QString const& band) const; bool CQ_zone_worked (int CQ_zone, QString const& mode, QString const& band) const; bool ITU_zone_worked (int ITU_zone, QString const& mode, QString const& band) const; + QString cty_version () const; - Q_SIGNAL void finished_loading (int worked_before_record_count, QString const& error) const; + Q_SIGNAL void finished_loading (int worked_before_record_count, QString const, QString const& error) const; private: class impl; diff --git a/logbook/logbook.cpp b/logbook/logbook.cpp index 2f2b70a5d..411d6240c 100644 --- a/logbook/logbook.cpp +++ b/logbook/logbook.cpp @@ -69,6 +69,11 @@ void LogBook::rescan () worked_before_.reload (); } +QString const LogBook::cty_version() const +{ + return worked_before_.cty_version(); +} + QByteArray LogBook::QSOToADIF (QString const& hisCall, QString const& hisGrid, QString const& mode, QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff, QString const& band, QString const& comments, diff --git a/logbook/logbook.h b/logbook/logbook.h index de7ffae10..96a17dd6e 100644 --- a/logbook/logbook.h +++ b/logbook/logbook.h @@ -46,7 +46,9 @@ public: QString const& m_myGrid, QString const& m_txPower, QString const& operator_call, QString const& xSent, QString const& xRcvd, QString const& propmode); - Q_SIGNAL void finished_loading (int worked_before_record_count, QString const& error) const; + QString const cty_version() const; + + Q_SIGNAL void finished_loading (int worked_before_record_count, QString const cty_version, QString const& error) const; CabrilloLog * contest_log (); Multiplier const * multiplier () const; diff --git a/translations/wsjtx_ca.ts b/translations/wsjtx_ca.ts index db7c4fe94..e16426840 100644 --- a/translations/wsjtx_ca.ts +++ b/translations/wsjtx_ca.ts @@ -3684,7 +3684,7 @@ La llista es pot mantenir a la configuració (F2).</translation> </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source> <translation>Log ADIF escanejat, %1 funcionava abans de la creació de registres</translation> </message> <message> diff --git a/translations/wsjtx_da.ts b/translations/wsjtx_da.ts index f9c632236..51d9d651b 100644 --- a/translations/wsjtx_da.ts +++ b/translations/wsjtx_da.ts @@ -3917,7 +3917,7 @@ listen. Makro listen kan også ændfres i Inderstillinger (F2).</translation> </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source> <translation>Scannet ADIF log, %1 worked B4 oprettede poster</translation> </message> <message> diff --git a/translations/wsjtx_en.ts b/translations/wsjtx_en.ts index 8d5413a8a..dfec1d917 100644 --- a/translations/wsjtx_en.ts +++ b/translations/wsjtx_en.ts @@ -3637,7 +3637,7 @@ list. The list can be maintained in Settings (F2).</source> </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source> <translation type="unfinished"></translation> </message> <message> diff --git a/translations/wsjtx_en_GB.ts b/translations/wsjtx_en_GB.ts index fe223f710..4d1fd1f44 100644 --- a/translations/wsjtx_en_GB.ts +++ b/translations/wsjtx_en_GB.ts @@ -3637,7 +3637,7 @@ list. The list can be maintained in Settings (F2).</source> </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created</source> <translation type="unfinished"></translation> </message> <message> diff --git a/translations/wsjtx_es.ts b/translations/wsjtx_es.ts index e2cc8caf7..3e79fdf4a 100644 --- a/translations/wsjtx_es.ts +++ b/translations/wsjtx_es.ts @@ -4237,7 +4237,7 @@ predefinida. La lista se puede modificar en "Ajustes" (F2).</translati </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source> <translatorcomment>Log ADIF escaneado, %1 funcionaba antes de la creación de registros</translatorcomment> <translation>Log ADIF escaneado, %1 registros trabajados B4 creados</translation> </message> diff --git a/translations/wsjtx_it.ts b/translations/wsjtx_it.ts index 590a15515..7f990d835 100644 --- a/translations/wsjtx_it.ts +++ b/translations/wsjtx_it.ts @@ -4112,7 +4112,7 @@ elenco. L'elenco può essere gestito in Impostazioni (F2).</translation> </message> <message> <location filename="../widgets/mainwindow.cpp" line="527"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source> <translation>Log ADIF scansionato,%1 ha funzionato prima della creazione dei record</translation> </message> <message> diff --git a/translations/wsjtx_ja.ts b/translations/wsjtx_ja.ts index e294cb461..ed9b26fad 100644 --- a/translations/wsjtx_ja.ts +++ b/translations/wsjtx_ja.ts @@ -3869,7 +3869,7 @@ ENTERを押してテキストを登録リストに追加. </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source> <translation>ADIFログ検索. %1交信済み記録作成しました</translation> </message> <message> diff --git a/translations/wsjtx_ru.ts b/translations/wsjtx_ru.ts index 409a72a13..1f6bfe189 100644 --- a/translations/wsjtx_ru.ts +++ b/translations/wsjtx_ru.ts @@ -3690,7 +3690,7 @@ list. The list can be maintained in Settings (F2).</source> </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source> <translation>Просканирован лог ADIF, %1 работал до создания записей</translation> </message> <message> diff --git a/translations/wsjtx_zh.ts b/translations/wsjtx_zh.ts index 93ef6c078..95f0bcf25 100644 --- a/translations/wsjtx_zh.ts +++ b/translations/wsjtx_zh.ts @@ -3683,7 +3683,7 @@ list. The list can be maintained in Settings (F2).</source> </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source> <translation>扫描 ADIF 日志, %1 创建曾经通联记录</translation> </message> <message> diff --git a/translations/wsjtx_zh_HK.ts b/translations/wsjtx_zh_HK.ts index 86f599a31..0efa2fe31 100644 --- a/translations/wsjtx_zh_HK.ts +++ b/translations/wsjtx_zh_HK.ts @@ -3683,7 +3683,7 @@ list. The list can be maintained in Settings (F2).</source> </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created</source> <translation>掃描 ADIF 紀錄, %1 建立曾經通聯紀錄</translation> </message> <message> diff --git a/translations/wsjtx_zh_TW.ts b/translations/wsjtx_zh_TW.ts index a4b23d7d3..66f27f1e6 100644 --- a/translations/wsjtx_zh_TW.ts +++ b/translations/wsjtx_zh_TW.ts @@ -3714,7 +3714,7 @@ list. The list can be maintained in Settings (F2).</source> </message> <message> <location filename="../widgets/mainwindow.cpp" line="523"/> - <source>Scanned ADIF log, %1 worked before records created</source> + <source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source> <translation>掃描 ADIF 紀錄, %1 建立曾經通聯紀錄</translation> </message> <message> diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 44cbc28ef..7994e13e1 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -522,14 +522,15 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect (this, &MainWindow::finished, m_logDlg.data (), &LogQSO::close); // hook up the log book - connect (&m_logBook, &LogBook::finished_loading, [this] (int record_count, QString const& error) { + connect (&m_logBook, &LogBook::finished_loading, [this] (int record_count, QString cty_version, QString const& error) { if (error.size ()) { MessageBox::warning_message (this, tr ("Error Scanning ADIF Log"), error); } else { - showStatusMessage (tr ("Scanned ADIF log, %1 worked before records created").arg (record_count)); + m_config.set_CTY_DAT_version(cty_version); + showStatusMessage (tr ("Scanned ADIF log, %1 worked-before records created. CTY: %2. CTY: %2").arg (record_count).arg (cty_version)); } }); From 88b1e46ee44477b940cd73f4fbea2086e969e4d0 Mon Sep 17 00:00:00 2001 From: Brian Moran <brian@trucentive.com> Date: Thu, 16 Mar 2023 19:13:59 -0700 Subject: [PATCH 06/14] use different temp file class; use VERYYYYMMMDD version from file --- Network/FileDownload.cpp | 35 ++++++++++++++++------------------- Network/FileDownload.hpp | 3 ++- logbook/AD1CCty.cpp | 15 +++++++++++++-- widgets/mainwindow.cpp | 2 +- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/Network/FileDownload.cpp b/Network/FileDownload.cpp index 7630ae59b..06c481e09 100644 --- a/Network/FileDownload.cpp +++ b/Network/FileDownload.cpp @@ -25,7 +25,8 @@ void FileDownload::errorOccurred(QNetworkReply::NetworkError code) { LOG_INFO(QString{"FileDownload [%1]: errorOccurred %2 -> %3"}.arg(user_agent_).arg(code).arg(reply_->errorString())); Q_EMIT error (reply_->errorString ()); - delete tmpfile_; + destfile_.cancelWriting (); + destfile_.commit (); } void FileDownload::configure(QNetworkAccessManager *network_manager, const QString &source_url, const QString &destination_path, const QString &user_agent) @@ -38,17 +39,16 @@ void FileDownload::configure(QNetworkAccessManager *network_manager, const QStri void FileDownload::store() { - if (tmpfile_->isOpen()) - tmpfile_->write (reply_->read (reply_->bytesAvailable ())); + if (destfile_.isOpen()) + destfile_.write (reply_->read (reply_->bytesAvailable ())); else - LOG_INFO(QString{ "FileDownload [%1]: tmpfile is not open"}.arg(user_agent_)); + LOG_INFO(QString{ "FileDownload [%1]: file is not open."}.arg(user_agent_)); } void FileDownload::replyComplete() { QFileInfo destination_file(destination_filename_); - QString const tmpfile_path = destination_file.absolutePath(); - QDir tmpdir_(tmpfile_path); + QDir tmpdir_(destination_file.absoluteFilePath()); LOG_DEBUG(QString{ "FileDownload [%1]: replyComplete"}.arg(user_agent_)); if (!reply_) @@ -83,8 +83,8 @@ void FileDownload::replyComplete() } else if (reply_->error () != QNetworkReply::NoError) { - tmpfile_->close(); - delete tmpfile_; + destfile_.cancelWriting(); + destfile_.commit(); url_valid_ = false; // reset // report errors that are not due to abort if (QNetworkReply::OperationCanceledError != reply_->error ()) @@ -107,14 +107,8 @@ void FileDownload::replyComplete() url_valid_ = false; // reset // load the database asynchronously // future_load_ = std::async (std::launch::async, &LotWUsers::impl::load_dictionary, this, csv_file_.fileName ()); - LOG_INFO(QString{ "FileDownload [%1]: complete. tempfile path is %2"}.arg(user_agent_).arg(tmpfile_->fileName())); - // move the file to the destination - tmpdir_.remove(destination_filename_+".old"); // get rid of previous version - tmpdir_.rename(destination_filename_, destination_filename_+".old"); - tmpdir_.rename(tmpfile_->fileName(), destination_filename_); - LOG_INFO(QString{ "FileDownload [%1]: moved tempfile %2 to %3"}.arg(user_agent_).arg(tmpfile_->fileName()).arg(destination_filename_)); - tmpfile_->close(); - delete tmpfile_; + LOG_INFO(QString{ "FileDownload [%1]: complete. File path is %2"}.arg(user_agent_).arg(destfile_.fileName())); + destfile_.commit(); emit complete(destination_filename_); } } @@ -184,15 +178,18 @@ void FileDownload::download(QUrl qurl) QObject::connect(manager_, &QNetworkAccessManager::finished, this, &FileDownload::downloadComplete, Qt::UniqueConnection); QObject::connect(reply_, &QNetworkReply::downloadProgress, this, &FileDownload::downloadProgress, Qt::UniqueConnection); QObject::connect(reply_, &QNetworkReply::finished, this,&FileDownload::replyComplete, Qt::UniqueConnection); - +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QObject::connect(reply_, &QNetworkReply::errorOccurred,this,&FileDownload::errorOccurred, Qt::UniqueConnection); +#else + QObject::connect(reply_, &QNetworkReply::error, this, &FileDownload::errorOccurred, Qt::UniqueConnection); +#endif QObject::connect (reply_, &QNetworkReply::readyRead, this, &FileDownload::store, Qt::UniqueConnection); QFileInfo destination_file(destination_filename_); QString const tmpfile_base = destination_file.fileName(); QString const tmpfile_path = destination_file.absolutePath(); - tmpfile_ = new QTemporaryFile(tmpfile_path+QDir::separator()+tmpfile_base+".XXXXXX"); - if (!tmpfile_->open()) + destfile_.setFileName(destination_file.absoluteFilePath()); + if (!destfile_.open(QSaveFile::WriteOnly)) { LOG_INFO(QString{"FileDownload [%1]: Unable to open the temporary file based on %2"}.arg(user_agent_).arg(tmpfile_path)); return; diff --git a/Network/FileDownload.hpp b/Network/FileDownload.hpp index b562cc925..03f31074e 100644 --- a/Network/FileDownload.hpp +++ b/Network/FileDownload.hpp @@ -7,6 +7,7 @@ #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> #include <QTemporaryFile> +#include <QSaveFile> class FileDownload : public QObject { Q_OBJECT @@ -24,7 +25,7 @@ private: QString user_agent_; QPointer<QNetworkReply> reply_; QNetworkRequest request_; - QPointer<QTemporaryFile> tmpfile_; + QSaveFile destfile_; bool url_valid_; int redirect_count_; signals: diff --git a/logbook/AD1CCty.cpp b/logbook/AD1CCty.cpp index 05a285430..5b42b8a9f 100644 --- a/logbook/AD1CCty.cpp +++ b/logbook/AD1CCty.cpp @@ -16,6 +16,7 @@ #include <QTextStream> #include <QDebug> #include <QDebugStateSaver> +#include <QRegularExpression> #include "Configuration.hpp" #include "Radio.hpp" #include "pimpl_impl.hpp" @@ -233,6 +234,8 @@ public: Configuration const * configuration_; QString path_; QString cty_version_; + QString cty_version_date_; + entities_type entities_; prefixes_type prefixes_; }; @@ -330,11 +333,14 @@ QString AD1CCty::impl::get_cty_path(Configuration const * configuration) void AD1CCty::impl::load_cty(QFile &file) { + QRegularExpression version_pattern{R"(VER\d{8})"}; int entity_id = 0; int line_number{0}; entities_.clear(); prefixes_.clear(); + cty_version_ = QString{}; + cty_version_date_ = QString{}; QTextStream in{&file}; while (!in.atEnd()) @@ -377,6 +383,11 @@ void AD1CCty::impl::load_cty(QFile &file) { prefix = prefix.mid(1); exact = true; + // match version pattern to prefix + if (version_pattern.match(prefix).hasMatch()) + { + cty_version_date_ = prefix; + } } prefixes_.emplace(prefix, exact, entity_id); } @@ -407,7 +418,7 @@ void AD1CCty::reload(Configuration const * configuration) m_->impl::load_cty(file); m_->cty_version_ = AD1CCty::lookup("VERSION").entity_name; Q_EMIT cty_loaded(m_->cty_version_); - LOG_INFO(QString{"Loaded CTY.DAT version %1"}.arg (m_->cty_version_)); + LOG_INFO(QString{"Loaded CTY.DAT version %1, %2"}.arg (m_->cty_version_date_).arg (m_->cty_version_)); } } @@ -448,5 +459,5 @@ auto AD1CCty::lookup (QString const& call) const -> Record } auto AD1CCty::version () const -> QString { - return m_->cty_version_; + return m_->cty_version_date_; } diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 7994e13e1..008232d54 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -530,7 +530,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, else { m_config.set_CTY_DAT_version(cty_version); - showStatusMessage (tr ("Scanned ADIF log, %1 worked-before records created. CTY: %2. CTY: %2").arg (record_count).arg (cty_version)); + showStatusMessage (tr ("Scanned ADIF log, %1 worked-before records created. CTY: %2").arg (record_count).arg (cty_version)); } }); From a7413ae6278e42fdf90c85c510890fa5fbc155df Mon Sep 17 00:00:00 2001 From: Brian Moran <brian.moran@gmail.com> Date: Thu, 16 Mar 2023 20:49:08 -0700 Subject: [PATCH 07/14] create the directory if one is supplied that doesn't exist --- Network/FileDownload.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Network/FileDownload.cpp b/Network/FileDownload.cpp index 06c481e09..3a306022c 100644 --- a/Network/FileDownload.cpp +++ b/Network/FileDownload.cpp @@ -7,7 +7,7 @@ #include <QtNetwork/QNetworkReply> #include <QFileInfo> #include <QDir> -#include <QTemporaryFile> +#include <QIODevice> #include "qt_helpers.hpp" #include "Logger.hpp" @@ -187,12 +187,21 @@ void FileDownload::download(QUrl qurl) QFileInfo destination_file(destination_filename_); QString const tmpfile_base = destination_file.fileName(); - QString const tmpfile_path = destination_file.absolutePath(); - destfile_.setFileName(destination_file.absoluteFilePath()); - if (!destfile_.open(QSaveFile::WriteOnly)) + QString const &tmpfile_path = destination_file.absolutePath(); + QDir tmpdir{}; + if (!tmpdir.mkpath(tmpfile_path)) { - LOG_INFO(QString{"FileDownload [%1]: Unable to open the temporary file based on %2"}.arg(user_agent_).arg(tmpfile_path)); - return; + LOG_INFO(QString{"FileDownload [%1]: Directory %2 does not exist"}.arg(user_agent_).arg(tmpfile_path).arg( + destfile_.errorString())); + } + + if (url_valid_) { + destfile_.setFileName(destination_file.absoluteFilePath()); + if (!destfile_.open(QSaveFile::WriteOnly | QIODevice::WriteOnly)) { + LOG_INFO(QString{"FileDownload [%1]: Unable to open %2: %3"}.arg(user_agent_).arg(destfile_.fileName()).arg( + destfile_.errorString())); + return; + } } } From 491f7832249f01d47d8dc9be69cb409cf70fe1e8 Mon Sep 17 00:00:00 2001 From: Uwe Risse <dg2ycb@gmx.de> Date: Fri, 17 Mar 2023 12:00:05 +0100 Subject: [PATCH 08/14] Disable line 184 as it doesn't compile with Qt 5.12.12 (and some minor cosmetic changes). --- Network/FileDownload.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Network/FileDownload.cpp b/Network/FileDownload.cpp index 3a306022c..4e88110dd 100644 --- a/Network/FileDownload.cpp +++ b/Network/FileDownload.cpp @@ -177,13 +177,13 @@ void FileDownload::download(QUrl qurl) QObject::connect(manager_, &QNetworkAccessManager::finished, this, &FileDownload::downloadComplete, Qt::UniqueConnection); QObject::connect(reply_, &QNetworkReply::downloadProgress, this, &FileDownload::downloadProgress, Qt::UniqueConnection); - QObject::connect(reply_, &QNetworkReply::finished, this,&FileDownload::replyComplete, Qt::UniqueConnection); + QObject::connect(reply_, &QNetworkReply::finished, this, &FileDownload::replyComplete, Qt::UniqueConnection); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) - QObject::connect(reply_, &QNetworkReply::errorOccurred,this,&FileDownload::errorOccurred, Qt::UniqueConnection); + QObject::connect(reply_, &QNetworkReply::errorOccurred,this, &FileDownload::errorOccurred, Qt::UniqueConnection); #else - QObject::connect(reply_, &QNetworkReply::error, this, &FileDownload::errorOccurred, Qt::UniqueConnection); +// QObject::connect(reply_, &QNetworkReply::error, this, &FileDownload::errorOccurred, Qt::UniqueConnection); #endif - QObject::connect (reply_, &QNetworkReply::readyRead, this, &FileDownload::store, Qt::UniqueConnection); + QObject::connect(reply_, &QNetworkReply::readyRead, this, &FileDownload::store, Qt::UniqueConnection); QFileInfo destination_file(destination_filename_); QString const tmpfile_base = destination_file.fileName(); @@ -217,4 +217,4 @@ void FileDownload::abort () { reply_->abort (); } -} \ No newline at end of file +} From 9151861c6d75de47ba2f1bed505b653b495cb231 Mon Sep 17 00:00:00 2001 From: Uwe Risse <dg2ycb@gmx.de> Date: Fri, 17 Mar 2023 12:02:41 +0100 Subject: [PATCH 09/14] Change "AppDataLocation" to "DataLocation". "AppDataLocation" is wrong on Windows as our log directory is at "DataLocation". --- Configuration.cpp | 2 +- logbook/AD1CCty.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Configuration.cpp b/Configuration.cpp index aed2c5417..fd28e634d 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -2425,7 +2425,7 @@ void Configuration::impl::on_rescan_log_push_button_clicked (bool /*clicked*/) void Configuration::impl::on_CTY_download_button_clicked (bool /*clicked*/) { ui_->CTY_download_button->setEnabled (false); // disable button until download is complete - QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::AppDataLocation)}; + QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}; cty_download.configure(network_manager_, "http://www.country-files.com/bigcty/cty.dat", dataPath.absoluteFilePath("cty.dat"), diff --git a/logbook/AD1CCty.cpp b/logbook/AD1CCty.cpp index 5b42b8a9f..526c6c8df 100644 --- a/logbook/AD1CCty.cpp +++ b/logbook/AD1CCty.cpp @@ -324,7 +324,7 @@ char const * AD1CCty::continent (Continent c) QString AD1CCty::impl::get_cty_path(Configuration const * configuration) { - QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::AppDataLocation)}; + QDir dataPath {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}; auto path = dataPath.exists (file_name) ? dataPath.absoluteFilePath (file_name) // user override : configuration->data_dir ().absoluteFilePath (file_name); // or original From 570e9ceaf721782e61819fab4bd78747b829dacb Mon Sep 17 00:00:00 2001 From: Uwe Risse <dg2ycb@gmx.de> Date: Fri, 17 Mar 2023 15:19:30 +0100 Subject: [PATCH 10/14] Make line 184 of FileDownload.cpp compliant with Qt 5.12.x. --- Network/FileDownload.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Network/FileDownload.cpp b/Network/FileDownload.cpp index 4e88110dd..6283efd89 100644 --- a/Network/FileDownload.cpp +++ b/Network/FileDownload.cpp @@ -181,7 +181,7 @@ void FileDownload::download(QUrl qurl) #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QObject::connect(reply_, &QNetworkReply::errorOccurred,this, &FileDownload::errorOccurred, Qt::UniqueConnection); #else -// QObject::connect(reply_, &QNetworkReply::error, this, &FileDownload::errorOccurred, Qt::UniqueConnection); + QObject::connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &FileDownload::errorOccurred, Qt::UniqueConnection); #endif QObject::connect(reply_, &QNetworkReply::readyRead, this, &FileDownload::store, Qt::UniqueConnection); From a321348dfaa10a9b8f038179f574b14157d1195c Mon Sep 17 00:00:00 2001 From: Brian Moran <brian@trucentive.com> Date: Fri, 17 Mar 2023 07:52:03 -0700 Subject: [PATCH 11/14] conditional compilation of error handler based on QT version --- Network/FileDownload.cpp | 13 +++++++++++-- Network/FileDownload.hpp | 4 ++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Network/FileDownload.cpp b/Network/FileDownload.cpp index 6283efd89..3ab2a7c6c 100644 --- a/Network/FileDownload.cpp +++ b/Network/FileDownload.cpp @@ -20,7 +20,7 @@ FileDownload::FileDownload() : QObject(nullptr) FileDownload::~FileDownload() { } - +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) void FileDownload::errorOccurred(QNetworkReply::NetworkError code) { LOG_INFO(QString{"FileDownload [%1]: errorOccurred %2 -> %3"}.arg(user_agent_).arg(code).arg(reply_->errorString())); @@ -28,6 +28,15 @@ void FileDownload::errorOccurred(QNetworkReply::NetworkError code) destfile_.cancelWriting (); destfile_.commit (); } +#else +void FileDownload::obsoleteError() +{ + LOG_INFO(QString{"FileDownload [%1]: error -> %3"}.arg(user_agent_).arg(reply_->errorString())); + Q_EMIT error (reply_->errorString ()); + destfile_.cancelWriting (); + destfile_.commit (); +} +#endif void FileDownload::configure(QNetworkAccessManager *network_manager, const QString &source_url, const QString &destination_path, const QString &user_agent) { @@ -181,7 +190,7 @@ void FileDownload::download(QUrl qurl) #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) QObject::connect(reply_, &QNetworkReply::errorOccurred,this, &FileDownload::errorOccurred, Qt::UniqueConnection); #else - QObject::connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &FileDownload::errorOccurred, Qt::UniqueConnection); + QObject::connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &FileDownload::obsoleteError, Qt::UniqueConnection); #endif QObject::connect(reply_, &QNetworkReply::readyRead, this, &FileDownload::store, Qt::UniqueConnection); diff --git a/Network/FileDownload.hpp b/Network/FileDownload.hpp index 03f31074e..c32948dd7 100644 --- a/Network/FileDownload.hpp +++ b/Network/FileDownload.hpp @@ -43,7 +43,11 @@ public slots: void abort(); void downloadComplete(QNetworkReply* data); void downloadProgress(qint64 recieved, qint64 total); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) void errorOccurred(QNetworkReply::NetworkError code); +#else + void obsoleteError(); +#endif void replyComplete(); }; From 5d61d2bf17bf70347f32862064eba7a6e3915b53 Mon Sep 17 00:00:00 2001 From: Brian Moran <brian@trucentive.com> Date: Fri, 17 Mar 2023 08:59:19 -0700 Subject: [PATCH 12/14] Show error dialog for CTY.DAT when there are error conditions --- Configuration.cpp | 9 +++++++++ widgets/mainwindow.cpp | 1 + 2 files changed, 10 insertions(+) diff --git a/Configuration.cpp b/Configuration.cpp index fd28e634d..061d2d9f0 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -567,6 +567,7 @@ private: void delete_selected_macros (QModelIndexList); void after_CTY_downloaded(); void set_CTY_DAT_version(QString const& version); + void error_during_CTY_download (QString const& reason); Q_SLOT void on_udp_server_line_edit_textChanged (QString const&); Q_SLOT void on_udp_server_line_edit_editingFinished (); Q_SLOT void on_save_path_select_push_button_clicked (bool); @@ -2433,6 +2434,8 @@ void Configuration::impl::on_CTY_download_button_clicked (bool /*clicked*/) // set up LoTW users CSV file fetching connect (&cty_download, &FileDownload::complete, this, &Configuration::impl::after_CTY_downloaded, Qt::UniqueConnection); + connect (&cty_download, &FileDownload::error, this, &Configuration::impl::error_during_CTY_download, Qt::UniqueConnection); + cty_download.start_download(); } void Configuration::impl::set_CTY_DAT_version(QString const& version) @@ -2440,6 +2443,12 @@ void Configuration::impl::set_CTY_DAT_version(QString const& version) ui_->CTY_file_label->setText(QString{"CTY File Version: %1"}.arg(version)); } +void Configuration::impl::error_during_CTY_download (QString const& reason) +{ + MessageBox::warning_message (this, tr ("Error Loading CTY.DAT"), reason); + after_CTY_downloaded(); +} + void Configuration::impl::after_CTY_downloaded () { ui_->CTY_download_button->setEnabled (true); diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 008232d54..bed378a45 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -647,6 +647,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, MessageBox::warning_message (this, tr ("Error Loading LotW Users Data"), reason); }, Qt::QueuedConnection); + QButtonGroup* txMsgButtonGroup = new QButtonGroup {this}; txMsgButtonGroup->addButton(ui->txrb1,1); txMsgButtonGroup->addButton(ui->txrb2,2); From c249d1fe7b65fa6b0cf2f4c331d9cc1981d1d407 Mon Sep 17 00:00:00 2001 From: Uwe Risse <dg2ycb@gmx.de> Date: Sun, 19 Mar 2023 21:00:50 +0100 Subject: [PATCH 13/14] Fix AutoSeq for the hound mode. --- widgets/mainwindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 51d3c4598..0848c13da 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -4206,6 +4206,7 @@ void MainWindow::readFromStdout() //readFromStdout m_rptRcvd=w.at(2); m_rptSent=decodedtext.string().mid(7,3); m_nFoxFreq=decodedtext.string().mid(16,4).toInt(); + hound_reply (); } else { if (text.contains(m_config.my_callsign() + " " + m_hisCall) && !text.contains("73 ")) processMessage(decodedtext0); // needed for MSHV multistream messages } From d08a056c8ec7fc6194e1450f94598168e6a264f7 Mon Sep 17 00:00:00 2001 From: Uwe Risse <dg2ycb@gmx.de> Date: Wed, 22 Mar 2023 12:05:04 +0100 Subject: [PATCH 14/14] Call FoxReset(); only when in Fox mode because it erases the decodedTextBrowser. --- widgets/mainwindow.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp index 0848c13da..beeb4b914 100644 --- a/widgets/mainwindow.cpp +++ b/widgets/mainwindow.cpp @@ -7837,9 +7837,8 @@ void MainWindow::band_changed (Frequency f) } setRig (f); setXIT (ui->TxFreqSpinBox->value ()); - - // when changing bands, don't preserve the Fox queues - FoxReset("BandChange"); + m_specOp=m_config.special_op_id(); + if (m_specOp==SpecOp::FOX) FoxReset("BandChange"); // when changing bands, don't preserve the Fox queues } }