diff --git a/CMakeLists.txt b/CMakeLists.txt
index bb4764e6f..3d9a8d89c 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 632537b8e..196a5c1ac 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,9 @@ 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);
+ 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);
@@ -574,7 +578,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 +752,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 +865,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;
@@ -1183,8 +1194,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"));
//
@@ -2414,9 +2430,45 @@ 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::DataLocation)};
+ 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);
+ 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)
+{
+ 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);
+ 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 13c54372b..bb2322c3d 100644
--- a/Configuration.hpp
+++ b/Configuration.hpp
@@ -244,6 +244,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 836b1b635..b3d38dda8 100644
--- a/Configuration.ui
+++ b/Configuration.ui
@@ -6,8 +6,8 @@
0
0
- 588
- 642
+ 684
+ 662
@@ -2407,8 +2407,14 @@ Right click for insert and delete options.
Logbook of the World User Validation
-
- -
+
+
+ 4
+
+
+ 4
+
+
-
Users CSV file URL:
@@ -2418,7 +2424,7 @@ Right click for insert and delete options.
- -
+
-
-
@@ -2445,7 +2451,7 @@ Right click for insert and delete options.
- -
+
-
Age of last upload less than:
@@ -2455,27 +2461,57 @@ Right click for insert and delete options.
- -
-
-
- <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>
-
-
- Days since last upload
-
-
- days
-
-
- 0
-
-
- 9999
-
-
- 365
-
-
+
-
+
+
-
+
+
+ <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>
+
+
+ Days since last upload
+
+
+ days
+
+
+ 0
+
+
+ 9999
+
+
+ 365
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 240
+ 0
+
+
+
+
+
+
+
+
@@ -2493,6 +2529,35 @@ Right click for insert and delete options.
+ -
+
+
+ CTY File Download
+
+
+
+ 4
+
+
+ 4
+
+
-
+
+
+ CTY File Version:
+
+
+
+ -
+
+
+ Download Latest CTY.dat
+
+
+
+
+
+
-
@@ -2501,7 +2566,7 @@ Right click for insert and delete options.
20
- 40
+ 20
diff --git a/Network/FileDownload.cpp b/Network/FileDownload.cpp
new file mode 100644
index 000000000..3ab2a7c6c
--- /dev/null
+++ b/Network/FileDownload.cpp
@@ -0,0 +1,229 @@
+
+#include "FileDownload.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "qt_helpers.hpp"
+#include "Logger.hpp"
+
+FileDownload::FileDownload() : QObject(nullptr)
+{
+ redirect_count_ = 0;
+ url_valid_ = false;
+}
+
+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()));
+ Q_EMIT error (reply_->errorString ());
+ 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)
+{
+ manager_ = network_manager;
+ source_url_ = source_url;
+ destination_filename_ = destination_path;
+ user_agent_ = user_agent;
+}
+
+void FileDownload::store()
+{
+ if (destfile_.isOpen())
+ destfile_.write (reply_->read (reply_->bytesAvailable ()));
+ else
+ LOG_INFO(QString{ "FileDownload [%1]: file is not open."}.arg(user_agent_));
+}
+
+void FileDownload::replyComplete()
+{
+ QFileInfo destination_file(destination_filename_);
+ QDir tmpdir_(destination_file.absoluteFilePath());
+
+ 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)
+ {
+ destfile_.cancelWriting();
+ destfile_.commit();
+ 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. File path is %2"}.arg(user_agent_).arg(destfile_.fileName()));
+ destfile_.commit();
+ emit complete(destination_filename_);
+ }
+ }
+
+ 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{"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))));
+ }
+
+ LOG_INFO("Response Headers:");
+ Q_FOREACH (const QByteArray& hdr, reply_->rawHeaderList()) {
+ LOG_INFO(QString{ "%1 -> %2"}.arg(QString(hdr)).arg(QString(reply_->rawHeader(hdr))));
+ }
+#endif
+ data->deleteLater();
+}
+
+void FileDownload::start_download()
+{
+ url_valid_ = false;
+ download(QUrl(source_url_));
+}
+
+void FileDownload::download(QUrl qurl)
+{
+ request_.setUrl(qurl);
+
+#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{"FileDownload [%1]: Starting download of %2 to %3"}.arg(user_agent_).arg(source_url_).arg(destination_filename_));
+
+ 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
+
+ if (!url_valid_)
+ {
+ reply_ = manager_->head(request_);
+ }
+ else
+ {
+ reply_ = manager_->get (request_);
+ }
+
+ 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_, QOverload::of(&QNetworkReply::error), this, &FileDownload::obsoleteError, 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();
+ QDir tmpdir{};
+ if (!tmpdir.mkpath(tmpfile_path))
+ {
+ 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;
+ }
+ }
+}
+
+void FileDownload::downloadProgress(qint64 received, qint64 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 ();
+ }
+}
diff --git a/Network/FileDownload.hpp b/Network/FileDownload.hpp
new file mode 100644
index 000000000..c32948dd7
--- /dev/null
+++ b/Network/FileDownload.hpp
@@ -0,0 +1,54 @@
+#ifndef WSJTX_FILEDOWNLOAD_H
+#define WSJTX_FILEDOWNLOAD_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class FileDownload : public QObject {
+ Q_OBJECT
+
+public:
+ explicit FileDownload();
+ ~FileDownload();
+
+ 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_;
+ QString user_agent_;
+ QPointer reply_;
+ QNetworkRequest request_;
+ QSaveFile destfile_;
+ 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 start_download();
+ void download(QUrl url);
+ void store();
+ 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();
+};
+
+#endif //WSJTX_FILEDOWNLOAD_H
diff --git a/Network/LotWUsers.cpp b/Network/LotWUsers.cpp
index 4e8024010..10a9a4d8b 100644
--- a/Network/LotWUsers.cpp
+++ b/Network/LotWUsers.cpp
@@ -16,7 +16,9 @@
#include
#include
#include
-
+#include "qt_helpers.hpp"
+#include "Logger.hpp"
+#include "FileDownload.hpp"
#include "pimpl_impl.hpp"
#include "moc_LotWUsers.cpp"
@@ -39,6 +41,7 @@ public:
, url_valid_ {false}
, redirect_count_ {0}
, age_constraint_ {365}
+ , connected_ {false}
{
}
@@ -48,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
{
@@ -67,142 +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
-
- 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 ()
- {
- 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
@@ -222,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;
}
@@ -241,6 +135,8 @@ public:
std::future future_load_;
dictionary last_uploaded_;
qint64 age_constraint_; // days
+ FileDownload lotw_downloader_;
+ bool connected_;
};
#include "LotWUsers.moc"
@@ -249,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..526c6c8df 100644
--- a/logbook/AD1CCty.cpp
+++ b/logbook/AD1CCty.cpp
@@ -16,9 +16,11 @@
#include
#include
#include
+#include
#include "Configuration.hpp"
#include "Radio.hpp"
#include "pimpl_impl.hpp"
+#include "Logger.hpp"
#include "moc_AD1CCty.cpp"
@@ -163,6 +165,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 +233,9 @@ public:
Configuration const * configuration_;
QString path_;
+ QString cty_version_;
+ QString cty_version_date_;
+
entities_type entities_;
prefixes_type prefixes_;
};
@@ -314,6 +322,80 @@ char const * AD1CCty::continent (Continent c)
}
}
+QString AD1CCty::impl::get_cty_path(Configuration const * configuration)
+{
+ 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
+ return path;
+}
+
+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())
+ {
+ 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 (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(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;
+ // match version pattern to prefix
+ if (version_pattern.match(prefix).hasMatch())
+ {
+ cty_version_date_ = prefix;
+ }
+ }
+ prefixes_.emplace(prefix, exact, entity_id);
+ }
+ }
+ }
+ }
+}
+
AD1CCty::AD1CCty (Configuration const * configuration)
: m_ {configuration}
{
@@ -321,69 +403,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 (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 (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, %2"}.arg (m_->cty_version_date_).arg (m_->cty_version_));
+ }
}
AD1CCty::~AD1CCty ()
@@ -421,3 +457,7 @@ auto AD1CCty::lookup (QString const& call) const -> Record
}
return Record {};
}
+auto AD1CCty::version () const -> QString
+{
+ return m_->cty_version_date_;
+}
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
#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).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created. CTY: %2
Log ADIF escanejat, %1 funcionava abans de la creació de registres
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).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created. CTY: %2
Scannet ADIF log, %1 worked B4 oprettede poster
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).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created. CTY: %2
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).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created
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).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created. CTY: %2
Log ADIF escaneado, %1 funcionaba antes de la creación de registros
Log ADIF escaneado, %1 registros trabajados B4 creados
diff --git a/translations/wsjtx_it.ts b/translations/wsjtx_it.ts
index 52a1ba5bd..ada4b4353 100644
--- a/translations/wsjtx_it.ts
+++ b/translations/wsjtx_it.ts
@@ -3891,7 +3891,7 @@ elenco. L'elenco può essere gestito in Impostazioni (F2).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created. CTY: %2
Log ADIF scansionato,%1 ha funzionato prima della creazione dei record
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を押してテキストを登録リストに追加.
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created. CTY: %2
ADIFログ検索. %1交信済み記録作成しました
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).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created. CTY: %2
Просканирован лог ADIF, %1 работал до создания записей
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).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created. CTY: %2
扫描 ADIF 日志, %1 创建曾经通联记录
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).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created
掃描 ADIF 紀錄, %1 建立曾經通聯紀錄
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).
- Scanned ADIF log, %1 worked before records created
+ Scanned ADIF log, %1 worked-before records created. CTY: %2
掃描 ADIF 紀錄, %1 建立曾經通聯紀錄
diff --git a/widgets/mainwindow.cpp b/widgets/mainwindow.cpp
index d42b29b76..beeb4b914 100644
--- a/widgets/mainwindow.cpp
+++ b/widgets/mainwindow.cpp
@@ -551,14 +551,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").arg (record_count).arg (cty_version));
}
});
@@ -675,6 +676,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);
@@ -4204,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
}
@@ -7834,6 +7837,8 @@ void MainWindow::band_changed (Frequency f)
}
setRig (f);
setXIT (ui->TxFreqSpinBox->value ());
+ m_specOp=m_config.special_op_id();
+ if (m_specOp==SpecOp::FOX) FoxReset("BandChange"); // when changing bands, don't preserve the Fox queues
}
}
@@ -9575,6 +9580,21 @@ 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();
+ m_discard_decoded_hounds_this_cycle = true; // discard decoded messages until the next cycle
+ if (reason != "") writeFoxQSO(" " + reason);
+ writeFoxQSO(" Reset");
+}
void MainWindow::on_pbFoxReset_clicked()
{
@@ -9582,16 +9602,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();
}
}
@@ -9715,6 +9726,7 @@ void MainWindow::selectHound(QString line, bool bTopQueue)
* 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
@@ -9759,6 +9771,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);
@@ -9974,26 +9995,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