| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | #include "FoxLog.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | #include <stdexcept>
 | 
					
						
							|  |  |  | #include <utility>
 | 
					
						
							| 
									
										
										
										
											2018-11-07 23:55:15 +00:00
										 |  |  | #include <QString>
 | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | #include <QDateTime>
 | 
					
						
							|  |  |  | #include <QSqlDatabase>
 | 
					
						
							|  |  |  | #include <QSqlTableModel>
 | 
					
						
							|  |  |  | #include <QSqlRecord>
 | 
					
						
							|  |  |  | #include <QSqlError>
 | 
					
						
							|  |  |  | #include <QSqlQuery>
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | #include <QTextStream>
 | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | #include <QDebug>
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | #include "Configuration.hpp"
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | #include "qt_db_helpers.hpp"
 | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | #include "pimpl_impl.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class FoxLog::impl final | 
					
						
							|  |  |  |   : public QSqlTableModel | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | public: | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  |   impl (Configuration const * configuration); | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  |   Configuration const * configuration_; | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  |   QSqlQuery mutable dupe_query_; | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  |   QSqlQuery mutable export_query_; | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | FoxLog::impl::impl (Configuration const * configuration) | 
					
						
							|  |  |  |   : configuration_ {configuration} | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | { | 
					
						
							|  |  |  |   if (!database ().tables ().contains ("fox_log")) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       QSqlQuery query; | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  |       SQL_error_check (query, static_cast<bool (QSqlQuery::*) (QString const&)> (&QSqlQuery::exec), | 
					
						
							|  |  |  |                        "CREATE TABLE fox_log (" | 
					
						
							|  |  |  |                        "	id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |                        "	\"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," | 
					
						
							|  |  |  |                        "	CONSTRAINT no_dupes UNIQUE (call, band)" | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  |                        ")"); | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  |   SQL_error_check (dupe_query_, &QSqlQuery::prepare, | 
					
						
							|  |  |  |                    "SELECT COUNT(*) FROM fox_log WHERE call = :call AND band = :band"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  |   SQL_error_check (export_query_, &QSqlQuery::prepare, | 
					
						
							|  |  |  |                    "SELECT band, \"when\", call, grid, report_sent, report_rcvd FROM fox_log ORDER BY \"when\""); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-02 02:30:32 +00:00
										 |  |  |   setEditStrategy (QSqlTableModel::OnFieldChange); | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |   setTable ("fox_log"); | 
					
						
							| 
									
										
										
										
											2018-11-12 04:00:55 +00:00
										 |  |  |   setHeaderData (fieldIndex ("when"), Qt::Horizontal, tr ("Date & Time(UTC)")); | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00: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 05:41:16 +00: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-12 02:06:44 +00:00
										 |  |  |   SQL_error_check (*this, &QSqlTableModel::select); | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00:00
										 |  |  | FoxLog::FoxLog (Configuration const * configuration) | 
					
						
							|  |  |  |   : m_ {configuration} | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | FoxLog::~FoxLog () | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  | QSqlTableModel * FoxLog::model () | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | { | 
					
						
							|  |  |  |   return &*m_; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00: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 17:49:45 +00:00
										 |  |  | bool FoxLog::add_QSO (QDateTime const& when, QString const& call, QString const& grid | 
					
						
							| 
									
										
										
										
											2018-11-12 18:26:05 +00:00
										 |  |  |                       , QString const& report_sent, QString const& report_received | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |                       , QString const& band) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   auto record = m_->record (); | 
					
						
							| 
									
										
										
										
											2018-11-23 01:18:39 +00:00
										 |  |  |   if (!when.isNull ()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       record.setValue ("when", when.toMSecsSinceEpoch () / 1000); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       record.setNull ("when"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   set_value_maybe_null (record, "call", call); | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00: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-23 01:18:39 +00:00
										 |  |  |   set_value_maybe_null (record, "band", band); | 
					
						
							| 
									
										
										
										
											2018-12-02 02:30:32 +00:00
										 |  |  |   if (m_->isDirty ()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       m_->revert ();            // discard any uncommitted changes
 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-12-06 05:41:16 +00:00
										 |  |  |   m_->setEditStrategy (QSqlTableModel::OnManualSubmit); | 
					
						
							|  |  |  |   ConditionalTransaction transaction {*m_}; | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  |   auto ok = m_->insertRecord (-1, record); | 
					
						
							|  |  |  |   if (ok) | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-12-06 05:41:16 +00:00
										 |  |  |       ok = transaction.submit (false); | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-12-06 05:41:16 +00:00
										 |  |  |   m_->setEditStrategy (QSqlTableModel::OnFieldChange); | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  |   return ok; | 
					
						
							| 
									
										
										
										
											2018-11-07 17:49:45 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00: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 () | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2018-11-12 04:00:55 +00:00
										 |  |  |   if (m_->rowCount ()) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  |       m_->setEditStrategy (QSqlTableModel::OnManualSubmit); | 
					
						
							| 
									
										
										
										
											2018-11-12 04:00:55 +00:00
										 |  |  |       ConditionalTransaction transaction {*m_}; | 
					
						
							|  |  |  |       SQL_error_check (*m_, &QSqlTableModel::removeRows, 0, m_->rowCount (), QModelIndex {}); | 
					
						
							|  |  |  |       transaction.submit (); | 
					
						
							| 
									
										
										
										
											2018-11-25 22:19:41 +00:00
										 |  |  |       m_->select ();            // to refresh views
 | 
					
						
							| 
									
										
										
										
											2018-12-02 02:30:32 +00:00
										 |  |  |       m_->setEditStrategy (QSqlTableModel::OnFieldChange); | 
					
						
							| 
									
										
										
										
											2018-11-12 04:00:55 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:44 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-12-30 12:35:41 +00: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; | 
					
						
							|  |  |  | } |