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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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 &quot;Ajustes&quot; (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&apos;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
   }
 }