Class abstracting LotW user lookups which includes asynchronous data loading

This commit is contained in:
Bill Somerville 2018-10-01 12:37:52 +01:00
parent d9cf9bed3c
commit 62a4569a4c
9 changed files with 141 additions and 120918 deletions

View File

@ -261,6 +261,7 @@ set (wsjt_qt_CXXSRCS
EqualizationToolsDialog.cpp EqualizationToolsDialog.cpp
DoubleClickablePushButton.cpp DoubleClickablePushButton.cpp
DoubleClickableRadioButton.cpp DoubleClickableRadioButton.cpp
LotWUsers.cpp
) )
set (wsjt_qtmm_CXXSRCS set (wsjt_qtmm_CXXSRCS

89
LotWUsers.cpp Normal file
View File

@ -0,0 +1,89 @@
#include "LotWUsers.hpp"
#include <future>
#include <QHash>
#include <QString>
#include <QDate>
#include <QFile>
#include <QTextStream>
#include <QDir>
#include <QDebug>
#include "Configuration.hpp"
#include "pimpl_impl.hpp"
#include "moc_LotWUsers.cpp"
namespace
{
// Dictionary mapping call sign to date of last upload to LotW
using dictionary = QHash<QString, QDate>;
// Load the database from the given file name
//
// Expects the file to be in CSV format with no header with one
// record per line. Record fields are call sign followed by upload
// date in yyyy-MM-dd format followed by upload time (ignored)
dictionary load (QString const& lotw_users_file)
{
dictionary result;
QFile f {lotw_users_file};
if (f.open (QFile::ReadOnly | QFile::Text))
{
QTextStream s {&f};
for (auto l = s.readLine (); !l.isNull (); l = s.readLine ())
{
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 ()).toLocal8Bit ()};
}
return result;
}
}
class LotWUsers::impl final
{
public:
std::future<dictionary> future_load_;
dictionary last_uploaded_;
};
LotWUsers::LotWUsers (Configuration const * configuration, QObject * parent)
: QObject {parent}
{
// load the database asynchronously
m_->future_load_ = std::async (std::launch::async, load, configuration->writeable_data_dir ().absoluteFilePath ("lotw-user-activity.csv"));
}
LotWUsers::~LotWUsers ()
{
}
bool LotWUsers::user (QString const& call, qint64 uploaded_since_days) const
{
if (m_->future_load_.valid ())
{
try
{
// wait for the load to finish if necessary
const_cast<dictionary&> (m_->last_uploaded_) = const_cast<std::future<dictionary>&> (m_->future_load_).get ();
}
catch (std::exception const& e)
{
Q_EMIT LotW_users_error (e.what ());
}
}
auto p = m_->last_uploaded_.constFind (call);
if (p != m_->last_uploaded_.end ())
{
return p.value ().daysTo (QDate::currentDate ()) <= uploaded_since_days;
}
return false;
}

35
LotWUsers.hpp Normal file
View File

@ -0,0 +1,35 @@
#ifndef LOTW_USERS_HPP_
#define LOTW_USERS_HPP_
#include <boost/core/noncopyable.hpp>
#include <QObject>
#include "pimpl_h.hpp"
class QString;
class QDate;
class Configuration;
//
// LotWUsers - Lookup Logbook of the World users
//
class LotWUsers final
: public QObject
{
Q_OBJECT
public:
LotWUsers (Configuration const * configuration, QObject * parent = 0);
~LotWUsers ();
// returns true if the specified call sign 'call' has uploaded their
// log to LotW in the last 'uploaded_since_days' days
Q_SLOT bool user (QString const& call, qint64 uploaded_since_days) const;
Q_SIGNAL void LotW_users_error (QString const& reason) const;
private:
class impl;
pimpl<impl> m_;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,6 @@ DecodedText::DecodedText (QString const& the_string)
, message_ {string_.mid (column_qsoText + padding_).trimmed ()} , message_ {string_.mid (column_qsoText + padding_).trimmed ()}
, is_standard_ {false} , is_standard_ {false}
{ {
qDebug () << "DecodedText: the_string:" << the_string << "Nbsp pos:" << the_string.indexOf (QChar::Nbsp);
if (message_.length() >= 1) if (message_.length() >= 1)
{ {
message0_ = message_.left(36); message0_ = message_.left(36);

View File

@ -1,5 +1,4 @@
#include "displaytext.h" #include "displaytext.h"
#include "mainwindow.h"
#include <QMouseEvent> #include <QMouseEvent>
#include <QDateTime> #include <QDateTime>
#include <QTextCharFormat> #include <QTextCharFormat>
@ -8,12 +7,14 @@
#include <QMenu> #include <QMenu>
#include <QAction> #include <QAction>
#include "qt_helpers.hpp" #include "LotWUsers.hpp"
#include "qt_helpers.hpp"
#include "moc_displaytext.cpp" #include "moc_displaytext.cpp"
DisplayText::DisplayText(QWidget *parent) DisplayText::DisplayText(QWidget *parent)
: QTextEdit(parent) : QTextEdit(parent)
, m_lotw_users {0}
, erase_action_ {new QAction {tr ("&Erase"), this}} , erase_action_ {new QAction {tr ("&Erase"), this}}
{ {
setReadOnly (true); setReadOnly (true);
@ -76,7 +77,6 @@ void DisplayText::insertLineSpacer(QString const& line)
void DisplayText::appendText(QString const& text, QColor bg, void DisplayText::appendText(QString const& text, QColor bg,
QString const& call1, QString const& call2) QString const& call1, QString const& call2)
{ {
qDebug () << "DisplayText::appendText: text:" << text << "Nbsp pos:" << text.indexOf (QChar::Nbsp);
auto cursor = textCursor (); auto cursor = textCursor ();
cursor.movePosition (QTextCursor::End); cursor.movePosition (QTextCursor::End);
auto block_format = cursor.blockFormat (); auto block_format = cursor.blockFormat ();
@ -144,7 +144,7 @@ void DisplayText::appendText(QString const& text, QColor bg,
} }
format.setBackground (bg); format.setBackground (bg);
format.clearForeground (); format.clearForeground ();
if(call2.size()>0 and !m_LoTW.contains(call2)) { if(call2.size () && !m_lotw_users->user (call2, 365)) {
format.setForeground(m_color_LoTW); //Mark LoTW non-users format.setForeground(m_color_LoTW); //Mark LoTW non-users
} }
cursor.insertText(text.mid (text_index), format); cursor.insertText(text.mid (text_index), format);

View File

@ -12,6 +12,7 @@
#include "decodedtext.h" #include "decodedtext.h"
class QAction; class QAction;
class LotWUsers;
class DisplayText class DisplayText
: public QTextEdit : public QTextEdit
@ -19,7 +20,7 @@ class DisplayText
Q_OBJECT Q_OBJECT
public: public:
explicit DisplayText(QWidget *parent = 0); explicit DisplayText(QWidget *parent = 0);
void setLotWUsers (LotWUsers const * lotw_users) {m_lotw_users = lotw_users;}
void setContentFont (QFont const&); void setContentFont (QFont const&);
void insertLineSpacer(QString const&); void insertLineSpacer(QString const&);
void displayDecodedText(DecodedText const& decodedText, QString const& myCall, void displayDecodedText(DecodedText const& decodedText, QString const& myCall,
@ -45,6 +46,7 @@ protected:
void mouseDoubleClickEvent(QMouseEvent *e); void mouseDoubleClickEvent(QMouseEvent *e);
private: private:
LotWUsers const * m_lotw_users;
bool m_bPrincipalPrefix; bool m_bPrincipalPrefix;
QString appendWorkedB4(QString message, QString const& callsign, QString grid, QColor * bg, QString appendWorkedB4(QString message, QString const& callsign, QString grid, QColor * bg,
LogBook const& logBook, QString currentBand); LogBook const& logBook, QString currentBand);

View File

@ -162,7 +162,6 @@ int fast_jhpeak {0};
int fast_jh2 {0}; int fast_jh2 {0};
int narg[15]; int narg[15];
QVector<QColor> g_ColorTbl; QVector<QColor> g_ColorTbl;
QHash<QString,int> m_LoTW;
namespace namespace
{ {
@ -204,6 +203,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_settings {multi_settings->settings ()}, m_settings {multi_settings->settings ()},
ui(new Ui::MainWindow), ui(new Ui::MainWindow),
m_config {temp_directory, m_settings, this}, m_config {temp_directory, m_settings, this},
m_lotw_users {&m_config},
m_WSPR_band_hopping {m_settings, &m_config, this}, m_WSPR_band_hopping {m_settings, &m_config, this},
m_WSPR_tx_next {false}, m_WSPR_tx_next {false},
m_rigErrorMessageBox {MessageBox::Critical, tr ("Rig Control Error") m_rigErrorMessageBox {MessageBox::Critical, tr ("Rig Control Error")
@ -386,6 +386,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
ui->dxGridEntry->setValidator (new MaidenheadLocatorValidator {this}); ui->dxGridEntry->setValidator (new MaidenheadLocatorValidator {this});
ui->dxCallEntry->setValidator (new CallsignValidator {this}); ui->dxCallEntry->setValidator (new CallsignValidator {this});
ui->sbTR->values ({5, 10, 15, 30}); ui->sbTR->values ({5, 10, 15, 30});
ui->decodedTextBrowser->setLotWUsers (&m_lotw_users);
ui->decodedTextBrowser2->setLotWUsers (&m_lotw_users);
m_baseCall = Radio::base_callsign (m_config.my_callsign ()); m_baseCall = Radio::base_callsign (m_config.my_callsign ());
m_opCall = m_config.opCall(); m_opCall = m_config.opCall();
@ -555,6 +557,10 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_equalizationToolsDialog->show (); m_equalizationToolsDialog->show ();
}); });
connect (&m_lotw_users, &LotWUsers::LotW_users_error, this, [this] (QString const& reason) {
MessageBox::warning_message (this, tr ("Error Loading LotW Users Data"), reason);
}, Qt::QueuedConnection);
QButtonGroup* txMsgButtonGroup = new QButtonGroup {this}; QButtonGroup* txMsgButtonGroup = new QButtonGroup {this};
txMsgButtonGroup->addButton(ui->txrb1,1); txMsgButtonGroup->addButton(ui->txrb1,1);
txMsgButtonGroup->addButton(ui->txrb2,2); txMsgButtonGroup->addButton(ui->txrb2,2);
@ -919,31 +925,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
ui->cbMenus->setChecked(false); ui->cbMenus->setChecked(false);
} }
QFile f{m_config.data_dir().absoluteFilePath ("lotw-user-activity.csv")};
if(f.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream s(&f);
QString line,call;
int nLoTW=0;
int i1;
QDateTime now=QDateTime::currentDateTime();
QDateTime callDateTime;
// Read and process the file of LoTW-active stations
while(!s.atEnd()) {
line=s.readLine();
i1=line.indexOf(",");
call=line.left(i1);
line=line.mid(i1+1);
i1=line.indexOf(",");
callDateTime=QDateTime::fromString(line.left(i1),"yyyy-MM-dd");
int ndays=callDateTime.daysTo(now);
if(ndays < 366) {
nLoTW++;
m_LoTW[call]=ndays;
}
}
f.close();
}
// this must be the last statement of constructor // this must be the last statement of constructor
if (!m_valid) throw std::runtime_error {"Fatal initialization exception"}; if (!m_valid) throw std::runtime_error {"Fatal initialization exception"};
} }

View File

@ -36,6 +36,7 @@
#include "astro.h" #include "astro.h"
#include "MessageBox.hpp" #include "MessageBox.hpp"
#include "NetworkAccessManager.hpp" #include "NetworkAccessManager.hpp"
#include "LotWUsers.hpp"
#define NUM_JT4_SYMBOLS 206 //(72+31)*2, embedded sync #define NUM_JT4_SYMBOLS 206 //(72+31)*2, embedded sync
#define NUM_JT65_SYMBOLS 126 //63 data + 63 sync #define NUM_JT65_SYMBOLS 126 //63 data + 63 sync
@ -341,8 +342,8 @@ private:
QSettings * m_settings; QSettings * m_settings;
QScopedPointer<Ui::MainWindow> ui; QScopedPointer<Ui::MainWindow> ui;
// other windows
Configuration m_config; Configuration m_config;
LotWUsers m_lotw_users;
WSPRBandHopping m_WSPR_band_hopping; WSPRBandHopping m_WSPR_band_hopping;
bool m_WSPR_tx_next; bool m_WSPR_tx_next;
MessageBox m_rigErrorMessageBox; MessageBox m_rigErrorMessageBox;