mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2025-03-21 19:48:54 -04:00
Merge branch 'develop' of bitbucket.org:k1jt/wsjtx into develop
This commit is contained in:
commit
c348791b28
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
121
Configuration.ui
121
Configuration.ui
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>588</width>
|
||||
<height>642</height>
|
||||
<width>684</width>
|
||||
<height>662</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -2407,8 +2407,14 @@ Right click for insert and delete options.</string>
|
||||
<property name="title">
|
||||
<string>Logbook of the World User Validation</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_18">
|
||||
<item row="1" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_13">
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Users CSV file URL:</string>
|
||||
@ -2418,7 +2424,7 @@ Right click for insert and delete options.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_20">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="LotW_CSV_URL_line_edit">
|
||||
@ -2445,7 +2451,7 @@ Right click for insert and delete options.</string>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Age of last upload less than:</string>
|
||||
@ -2455,27 +2461,57 @@ Right click for insert and delete options.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="LotW_days_since_upload_spin_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.</p></body></html></string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Days since last upload</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> days</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9999</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>365</number>
|
||||
</property>
|
||||
</widget>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_23">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="LotW_days_since_upload_spin_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.</p></body></html></string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Days since last upload</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> days</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9999</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>365</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_13">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="LotW_CSV_status_label">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>240</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -2493,6 +2529,35 @@ Right click for insert and delete options.</string>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_8">
|
||||
<property name="title">
|
||||
<string>CTY File Download</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_17">
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="CTY_file_label">
|
||||
<property name="text">
|
||||
<string>CTY File Version: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="CTY_download_button">
|
||||
<property name="text">
|
||||
<string>Download Latest CTY.dat</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
@ -2501,7 +2566,7 @@ Right click for insert and delete options.</string>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
|
229
Network/FileDownload.cpp
Normal file
229
Network/FileDownload.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
|
||||
#include "FileDownload.hpp"
|
||||
#include <QCoreApplication>
|
||||
#include <QUrl>
|
||||
#include <QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QIODevice>
|
||||
#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<QNetworkReply::NetworkError>::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 ();
|
||||
}
|
||||
}
|
54
Network/FileDownload.hpp
Normal file
54
Network/FileDownload.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef WSJTX_FILEDOWNLOAD_H
|
||||
#define WSJTX_FILEDOWNLOAD_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QPointer>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QTemporaryFile>
|
||||
#include <QSaveFile>
|
||||
|
||||
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<QNetworkReply> 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
|
@ -16,7 +16,9 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QDebug>
|
||||
|
||||
#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<dictionary> 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 ()
|
||||
|
@ -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:
|
||||
|
@ -16,9 +16,11 @@
|
||||
#include <QTextStream>
|
||||
#include <QDebug>
|
||||
#include <QDebugStateSaver>
|
||||
#include <QRegularExpression>
|
||||
#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<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;
|
||||
// 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<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, %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_;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -4237,7 +4237,7 @@ predefinida. La lista se puede modificar en "Ajustes" (F2).</translati
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widgets/mainwindow.cpp" line="523"/>
|
||||
<source>Scanned ADIF log, %1 worked before records created</source>
|
||||
<source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source>
|
||||
<translatorcomment>Log ADIF escaneado, %1 funcionaba antes de la creación de registros</translatorcomment>
|
||||
<translation>Log ADIF escaneado, %1 registros trabajados B4 creados</translation>
|
||||
</message>
|
||||
|
@ -3891,7 +3891,7 @@ elenco. L'elenco può essere gestito in Impostazioni (F2).</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../widgets/mainwindow.cpp" line="527"/>
|
||||
<source>Scanned ADIF log, %1 worked before records created</source>
|
||||
<source>Scanned ADIF log, %1 worked-before records created. CTY: %2</source>
|
||||
<translation>Log ADIF scansionato,%1 ha funzionato prima della creazione dei record</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
* <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
|
||||
@ -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<n2 and fm=="") {
|
||||
|
@ -329,12 +329,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
|
||||
@ -570,6 +571,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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user