2018-11-07 12:49:45 -05:00
|
|
|
#include "FoxLog.hpp"
|
|
|
|
|
2018-11-11 21:06:44 -05:00
|
|
|
#include <stdexcept>
|
|
|
|
#include <utility>
|
2018-11-07 18:55:15 -05:00
|
|
|
#include <QString>
|
2018-11-07 12:49:45 -05:00
|
|
|
#include <QDateTime>
|
|
|
|
#include <QSqlDatabase>
|
|
|
|
#include <QSqlTableModel>
|
|
|
|
#include <QSqlRecord>
|
|
|
|
#include <QSqlError>
|
|
|
|
#include <QSqlQuery>
|
2018-12-30 07:35:41 -05:00
|
|
|
#include <QTextStream>
|
2018-11-07 12:49:45 -05:00
|
|
|
#include <QDebug>
|
2018-12-30 07:35:41 -05:00
|
|
|
#include "Configuration.hpp"
|
2018-11-11 21:06:44 -05:00
|
|
|
#include "qt_db_helpers.hpp"
|
2018-11-07 12:49:45 -05:00
|
|
|
#include "pimpl_impl.hpp"
|
|
|
|
|
|
|
|
class FoxLog::impl final
|
|
|
|
: public QSqlTableModel
|
|
|
|
{
|
2019-06-06 07:56:25 -04:00
|
|
|
Q_OBJECT
|
|
|
|
|
2018-11-07 12:49:45 -05:00
|
|
|
public:
|
2018-12-30 07:35:41 -05:00
|
|
|
impl (Configuration const * configuration);
|
2018-11-11 21:06:44 -05:00
|
|
|
|
2019-05-03 05:21:50 -04:00
|
|
|
QVariant data (QModelIndex const& index, int role) const
|
|
|
|
{
|
|
|
|
auto value = QSqlTableModel::data (index, role);
|
|
|
|
if (index.column () == fieldIndex ("when")
|
|
|
|
&& (Qt::DisplayRole == role || Qt::EditRole == role))
|
|
|
|
{
|
|
|
|
auto t = QDateTime::fromMSecsSinceEpoch (value.toULongLong () * 1000ull, Qt::UTC);
|
|
|
|
if (Qt::DisplayRole == role)
|
|
|
|
{
|
|
|
|
QLocale locale;
|
|
|
|
return locale.toString (t, locale.dateFormat (QLocale::ShortFormat) + " hh:mm:ss");
|
|
|
|
}
|
|
|
|
value = t;
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2018-12-30 07:35:41 -05:00
|
|
|
Configuration const * configuration_;
|
2018-11-11 21:06:44 -05:00
|
|
|
QSqlQuery mutable dupe_query_;
|
2018-12-30 07:35:41 -05:00
|
|
|
QSqlQuery mutable export_query_;
|
2018-11-07 12:49:45 -05:00
|
|
|
};
|
|
|
|
|
2019-06-06 07:56:25 -04:00
|
|
|
#include "FoxLog.moc"
|
|
|
|
|
2019-11-18 11:45:16 -05:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
QString const fox_log_ddl {
|
|
|
|
"CREATE %1 TABLE fox_log%2 ("
|
|
|
|
" id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
|
|
|
|
" \"when\" DATETIME NOT NULL,"
|
|
|
|
" call VARCHAR(20) NOT NULL,"
|
|
|
|
" grid VARCHAR(4),"
|
|
|
|
" report_sent VARCHAR(3),"
|
|
|
|
" report_rcvd VARCHAR(3),"
|
|
|
|
" band VARCHAR(6) NOT NULL"
|
|
|
|
")"
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-12-30 07:35:41 -05:00
|
|
|
FoxLog::impl::impl (Configuration const * configuration)
|
|
|
|
: configuration_ {configuration}
|
2018-11-07 12:49:45 -05:00
|
|
|
{
|
|
|
|
if (!database ().tables ().contains ("fox_log"))
|
|
|
|
{
|
|
|
|
QSqlQuery query;
|
2018-11-11 21:06:44 -05:00
|
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
2019-11-18 11:45:16 -05:00
|
|
|
fox_log_ddl.arg ("").arg (""));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QSqlQuery query;
|
|
|
|
// query to check if table has a unique constraint
|
|
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
|
|
|
"SELECT COUNT(*)"
|
|
|
|
" FROM sqlite_master"
|
|
|
|
" WHERE"
|
|
|
|
" type = 'index' AND tbl_name = 'fox_log'");
|
|
|
|
query.next ();
|
|
|
|
if (query.value (0).toInt ())
|
|
|
|
{
|
|
|
|
// update to new schema with no dupe disallowing unique
|
|
|
|
// constraint
|
|
|
|
database ().transaction ();
|
|
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
|
|
|
fox_log_ddl.arg ("TEMPORARY").arg ("_backup"));
|
|
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
|
|
|
"INSERT INTO fox_log_backup SELECT * from fox_log");
|
|
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
|
|
|
"DROP TABLE fox_log");
|
|
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
|
|
|
fox_log_ddl.arg ("").arg (""));
|
|
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
|
|
|
"INSERT INTO fox_log SELECT * from fox_log_backup");
|
|
|
|
SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec),
|
|
|
|
"DROP TABLE fox_log_backup");
|
|
|
|
database ().commit ();
|
|
|
|
}
|
2018-11-07 12:49:45 -05:00
|
|
|
}
|
|
|
|
|
2018-11-11 21:06:44 -05:00
|
|
|
SQL_error_check (dupe_query_, &QSqlQuery::prepare,
|
2019-01-21 08:35:18 -05:00
|
|
|
"SELECT "
|
|
|
|
" COUNT(*) "
|
|
|
|
" FROM "
|
|
|
|
" fox_log "
|
|
|
|
" WHERE "
|
|
|
|
" call = :call "
|
|
|
|
" AND band = :band");
|
2018-11-11 21:06:44 -05:00
|
|
|
|
2018-12-30 07:35:41 -05:00
|
|
|
SQL_error_check (export_query_, &QSqlQuery::prepare,
|
2019-01-21 08:35:18 -05:00
|
|
|
"SELECT "
|
|
|
|
" band"
|
|
|
|
" , \"when\""
|
|
|
|
" , call"
|
|
|
|
" , grid"
|
|
|
|
" , report_sent"
|
|
|
|
" , report_rcvd "
|
|
|
|
" FROM "
|
|
|
|
" fox_log "
|
|
|
|
" ORDER BY "
|
|
|
|
" \"when\"");
|
2018-12-30 07:35:41 -05:00
|
|
|
|
2018-12-01 21:30:32 -05:00
|
|
|
setEditStrategy (QSqlTableModel::OnFieldChange);
|
2018-11-07 12:49:45 -05:00
|
|
|
setTable ("fox_log");
|
2018-11-11 23:00:55 -05:00
|
|
|
setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)"));
|
2018-11-07 12:49:45 -05:00
|
|
|
setHeaderData (fieldIndex ("call"), Qt::Horizontal, tr ("Call"));
|
|
|
|
setHeaderData (fieldIndex ("grid"), Qt::Horizontal, tr ("Grid"));
|
|
|
|
setHeaderData (fieldIndex ("report_sent"), Qt::Horizontal, tr ("Sent"));
|
|
|
|
setHeaderData (fieldIndex ("report_rcvd"), Qt::Horizontal, tr ("Rcvd"));
|
|
|
|
setHeaderData (fieldIndex ("band"), Qt::Horizontal, tr ("Band"));
|
2018-12-06 00:41:16 -05:00
|
|
|
|
|
|
|
// This descending order by time is important, it makes the view
|
|
|
|
// place the latest row at the top, without this the model/view
|
|
|
|
// interactions are both sluggish and unhelpful.
|
|
|
|
setSort (fieldIndex ("when"), Qt::DescendingOrder);
|
|
|
|
|
2018-11-11 21:06:44 -05:00
|
|
|
SQL_error_check (*this, &QSqlTableModel::select);
|
2018-11-07 12:49:45 -05:00
|
|
|
}
|
|
|
|
|
2018-12-30 07:35:41 -05:00
|
|
|
FoxLog::FoxLog (Configuration const * configuration)
|
|
|
|
: m_ {configuration}
|
2018-11-07 12:49:45 -05:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
FoxLog::~FoxLog ()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2018-11-25 17:19:41 -05:00
|
|
|
QSqlTableModel * FoxLog::model ()
|
2018-11-07 12:49:45 -05:00
|
|
|
{
|
|
|
|
return &*m_;
|
|
|
|
}
|
|
|
|
|
2018-11-11 21:06:44 -05:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
void set_value_maybe_null (QSqlRecord& record, QString const& name, QString const& value)
|
|
|
|
{
|
|
|
|
if (value.size ())
|
|
|
|
{
|
|
|
|
record.setValue (name, value);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
record.setNull (name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-07 12:49:45 -05:00
|
|
|
bool FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& grid
|
2018-11-12 13:26:05 -05:00
|
|
|
, QString const& report_sent, QString const& report_received
|
2018-11-07 12:49:45 -05:00
|
|
|
, QString const& band)
|
|
|
|
{
|
|
|
|
auto record = m_->record ();
|
2018-11-22 20:18:39 -05:00
|
|
|
if (!when.isNull ())
|
|
|
|
{
|
|
|
|
record.setValue ("when", when.toMSecsSinceEpoch () / 1000);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
record.setNull ("when");
|
|
|
|
}
|
|
|
|
set_value_maybe_null (record, "call", call);
|
2018-11-11 21:06:44 -05:00
|
|
|
set_value_maybe_null (record, "grid", grid);
|
|
|
|
set_value_maybe_null (record, "report_sent", report_sent);
|
|
|
|
set_value_maybe_null (record, "report_rcvd", report_received);
|
2018-11-22 20:18:39 -05:00
|
|
|
set_value_maybe_null (record, "band", band);
|
2018-12-01 21:30:32 -05:00
|
|
|
if (m_->isDirty ())
|
|
|
|
{
|
|
|
|
m_->revert (); // discard any uncommitted changes
|
|
|
|
}
|
2018-12-06 00:41:16 -05:00
|
|
|
m_->setEditStrategy (QSqlTableModel::OnManualSubmit);
|
|
|
|
ConditionalTransaction transaction {*m_};
|
2018-11-25 17:19:41 -05:00
|
|
|
auto ok = m_->insertRecord (-1, record);
|
|
|
|
if (ok)
|
2018-11-07 12:49:45 -05:00
|
|
|
{
|
2018-12-06 00:41:16 -05:00
|
|
|
ok = transaction.submit (false);
|
2018-11-07 12:49:45 -05:00
|
|
|
}
|
2018-12-06 00:41:16 -05:00
|
|
|
m_->setEditStrategy (QSqlTableModel::OnFieldChange);
|
2018-11-25 17:19:41 -05:00
|
|
|
return ok;
|
2018-11-07 12:49:45 -05:00
|
|
|
}
|
2018-11-11 21:06:44 -05:00
|
|
|
|
|
|
|
bool FoxLog::dupe (QString const& call, QString const& band) const
|
|
|
|
{
|
|
|
|
m_->dupe_query_.bindValue (":call", call);
|
|
|
|
m_->dupe_query_.bindValue (":band", band);
|
|
|
|
SQL_error_check (m_->dupe_query_, static_cast<bool (QSqlQuery::*) ()> (&QSqlQuery::exec));
|
|
|
|
m_->dupe_query_.next ();
|
|
|
|
return m_->dupe_query_.value (0).toInt ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FoxLog::reset ()
|
|
|
|
{
|
2019-01-21 08:35:18 -05:00
|
|
|
// synchronize model
|
|
|
|
while (m_->canFetchMore ()) m_->fetchMore ();
|
2018-11-11 23:00:55 -05:00
|
|
|
if (m_->rowCount ())
|
|
|
|
{
|
2018-11-25 17:19:41 -05:00
|
|
|
m_->setEditStrategy (QSqlTableModel::OnManualSubmit);
|
2018-11-11 23:00:55 -05:00
|
|
|
ConditionalTransaction transaction {*m_};
|
|
|
|
SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {});
|
|
|
|
transaction.submit ();
|
2018-11-25 17:19:41 -05:00
|
|
|
m_->select (); // to refresh views
|
2018-12-01 21:30:32 -05:00
|
|
|
m_->setEditStrategy (QSqlTableModel::OnFieldChange);
|
2018-11-11 23:00:55 -05:00
|
|
|
}
|
2018-11-11 21:06:44 -05:00
|
|
|
}
|
2018-12-30 07:35:41 -05:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
struct ADIF_field
|
|
|
|
{
|
|
|
|
explicit ADIF_field (QString const& name, QString const& value)
|
|
|
|
: name_ {name}
|
|
|
|
, value_ {value}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
QString name_;
|
|
|
|
QString value_;
|
|
|
|
};
|
|
|
|
|
|
|
|
QTextStream& operator << (QTextStream& os, ADIF_field const& field)
|
|
|
|
{
|
|
|
|
if (field.value_.size ())
|
|
|
|
{
|
|
|
|
os << QString {"<%1:%2>%3 "}.arg (field.name_).arg (field.value_.size ()).arg (field.value_);
|
|
|
|
}
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FoxLog::export_qsos (QTextStream& out) const
|
|
|
|
{
|
|
|
|
out << "WSJT-X FT8 DXpedition Mode Fox Log\n<eoh>";
|
|
|
|
|
|
|
|
SQL_error_check (m_->export_query_, static_cast<bool (QSqlQuery::*) ()> (&QSqlQuery::exec));
|
|
|
|
auto record = m_->export_query_.record ();
|
|
|
|
auto band_index = record.indexOf ("band");
|
|
|
|
auto when_index = record.indexOf ("when");
|
|
|
|
auto call_index = record.indexOf ("call");
|
|
|
|
auto grid_index = record.indexOf ("grid");
|
|
|
|
auto sent_index = record.indexOf ("report_sent");
|
|
|
|
auto rcvd_index = record.indexOf ("report_rcvd");
|
|
|
|
while (m_->export_query_.next ())
|
|
|
|
{
|
|
|
|
auto when = QDateTime::fromMSecsSinceEpoch (m_->export_query_.value (when_index).toULongLong () * 1000ull, Qt::UTC);
|
|
|
|
out << '\n'
|
|
|
|
<< ADIF_field {"band", m_->export_query_.value (band_index).toString ()}
|
|
|
|
<< ADIF_field {"mode", "FT8"}
|
|
|
|
<< ADIF_field {"qso_date", when.toString ("yyyyMMdd")}
|
|
|
|
<< ADIF_field {"time_on", when.toString ("hhmmss")}
|
|
|
|
<< ADIF_field {"call", m_->export_query_.value (call_index).toString ()}
|
|
|
|
<< ADIF_field {"gridsquare", m_->export_query_.value (grid_index).toString ()}
|
|
|
|
<< ADIF_field {"rst_sent", m_->export_query_.value (sent_index).toString ()}
|
|
|
|
<< ADIF_field {"rst_rcvd", m_->export_query_.value (rcvd_index).toString ()}
|
|
|
|
<< ADIF_field {"station_callsign", m_->configuration_->my_callsign ()}
|
|
|
|
<< ADIF_field {"my_gridsquare", m_->configuration_->my_grid ()}
|
|
|
|
<< ADIF_field {"operator", m_->configuration_->opCall ()}
|
|
|
|
<< "<eor>";
|
|
|
|
}
|
|
|
|
out << endl;
|
|
|
|
}
|