| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  | #include "LotWUsers.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <future>
 | 
					
						
							| 
									
										
										
										
											2019-01-01 16:19:01 +00:00
										 |  |  | #include <chrono>
 | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include <QHash>
 | 
					
						
							|  |  |  | #include <QString>
 | 
					
						
							|  |  |  | #include <QDate>
 | 
					
						
							|  |  |  | #include <QFile>
 | 
					
						
							|  |  |  | #include <QTextStream>
 | 
					
						
							|  |  |  | #include <QDir>
 | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  | #include <QFileInfo>
 | 
					
						
							|  |  |  | #include <QPointer>
 | 
					
						
							|  |  |  | #include <QSaveFile>
 | 
					
						
							|  |  |  | #include <QUrl>
 | 
					
						
							|  |  |  | #include <QNetworkAccessManager>
 | 
					
						
							|  |  |  | #include <QNetworkReply>
 | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  | #include <QDebug>
 | 
					
						
							| 
									
										
										
										
											2023-03-09 12:10:20 -08:00
										 |  |  | #include "qt_helpers.hpp"
 | 
					
						
							|  |  |  | #include "Logger.hpp"
 | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  | #include "FileDownload.hpp"
 | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  | #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>; | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class LotWUsers::impl final | 
					
						
							|  |  |  |   : public QObject | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   Q_OBJECT | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | public: | 
					
						
							| 
									
										
										
										
											2018-10-01 22:43:13 +01:00
										 |  |  |   impl (LotWUsers * self, QNetworkAccessManager * network_manager) | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |     : self_ {self} | 
					
						
							|  |  |  |     , network_manager_ {network_manager} | 
					
						
							|  |  |  |     , url_valid_ {false} | 
					
						
							|  |  |  |     , redirect_count_ {0} | 
					
						
							| 
									
										
										
										
											2018-10-17 00:26:04 +01:00
										 |  |  |     , age_constraint_ {365} | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |     , connected_ {false} | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |   { | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-02 13:22:42 +00:00
										 |  |  |   void load (QString const& url, bool fetch, bool forced_fetch) | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |   { | 
					
						
							|  |  |  |     abort ();                   // abort any active download
 | 
					
						
							| 
									
										
										
										
											2019-03-02 13:22:42 +00:00
										 |  |  |     auto csv_file_name = csv_file_.fileName (); | 
					
						
							|  |  |  |     auto exists = QFileInfo::exists (csv_file_name); | 
					
						
							|  |  |  |     if (fetch && (!exists || forced_fetch)) | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |     { | 
					
						
							|  |  |  |       current_url_.setUrl(url); | 
					
						
							|  |  |  |       if (current_url_.isValid() && !QSslSocket::supportsSsl()) | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |       { | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |         current_url_.setScheme("http"); | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |       redirect_count_ = 0; | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |       Q_EMIT self_->progress (QString("Starting download from %1").arg(url)); | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |       lotw_downloader_.configure(network_manager_, | 
					
						
							|  |  |  |                                  url, | 
					
						
							|  |  |  |                                  csv_file_name, | 
					
						
							|  |  |  |                                  "WSJT-X LotW User Downloader"); | 
					
						
							|  |  |  |       if (!connected_) | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |       { | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |         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; | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |         lotw_downloader_.start_download(); | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |       } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |         if (exists) | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |           { | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |             // load the database asynchronously
 | 
					
						
							|  |  |  |             future_load_ = std::async (std::launch::async, &LotWUsers::impl::load_dictionary, this, csv_file_name); | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |           } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void abort () | 
					
						
							|  |  |  |   { | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |     lotw_downloader_.abort(); | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // 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)
 | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |   dictionary load_dictionary (QString const& lotw_csv_file) | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |   { | 
					
						
							|  |  |  |     dictionary result; | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |     QFile f {lotw_csv_file}; | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |     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"); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2018-10-03 19:18:54 +01:00
										 |  |  |         throw std::runtime_error {QObject::tr ("Failed to open LotW users CSV file: '%1'").arg (f.fileName ()).toStdString ()}; | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |     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(); | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |     return result; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  |   LotWUsers * self_; | 
					
						
							|  |  |  |   QNetworkAccessManager * network_manager_; | 
					
						
							|  |  |  |   QSaveFile csv_file_; | 
					
						
							|  |  |  |   bool url_valid_; | 
					
						
							|  |  |  |   QUrl current_url_;            // may be a redirect
 | 
					
						
							|  |  |  |   int redirect_count_; | 
					
						
							|  |  |  |   QPointer<QNetworkReply> reply_; | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |   std::future<dictionary> future_load_; | 
					
						
							|  |  |  |   dictionary last_uploaded_; | 
					
						
							| 
									
										
										
										
											2018-10-17 00:26:04 +01:00
										 |  |  |   qint64 age_constraint_;       // days
 | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  |   FileDownload lotw_downloader_; | 
					
						
							|  |  |  |   bool connected_; | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  | #include "LotWUsers.moc"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-01 22:43:13 +01:00
										 |  |  | LotWUsers::LotWUsers (QNetworkAccessManager * network_manager, QObject * parent) | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |   : QObject {parent} | 
					
						
							| 
									
										
										
										
											2018-10-01 22:43:13 +01:00
										 |  |  |   , m_ {this, network_manager} | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2023-03-15 20:42:03 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | LotWUsers::~LotWUsers () | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-17 00:26:04 +01:00
										 |  |  | void LotWUsers::set_local_file_path (QString const& path) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   m_->csv_file_.setFileName (path); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-02 13:22:42 +00:00
										 |  |  | void LotWUsers::load (QString const& url, bool fetch, bool force_download) | 
					
						
							| 
									
										
										
										
											2018-10-17 00:26:04 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-02 13:22:42 +00:00
										 |  |  |   m_->load (url, fetch, force_download); | 
					
						
							| 
									
										
										
										
											2018-10-17 00:26:04 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void LotWUsers::set_age_constraint (qint64 uploaded_since_days) | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2018-10-17 00:26:04 +01:00
										 |  |  |   m_->age_constraint_ = uploaded_since_days; | 
					
						
							| 
									
										
										
										
											2018-10-01 21:19:21 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-17 00:26:04 +01:00
										 |  |  | bool LotWUsers::user (QString const& call) const | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-01-01 16:19:01 +00:00
										 |  |  |   // check if a pending asynchronous load is ready
 | 
					
						
							|  |  |  |   if (m_->future_load_.valid () | 
					
						
							|  |  |  |       && std::future_status::ready == m_->future_load_.wait_for (std::chrono::seconds {0})) | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |     { | 
					
						
							|  |  |  |       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 ()); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-10-17 00:26:04 +01:00
										 |  |  |       Q_EMIT load_finished (); | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-01-01 16:19:01 +00:00
										 |  |  |   if (m_->last_uploaded_.size ()) | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2019-01-01 16:19:01 +00:00
										 |  |  |       auto p = m_->last_uploaded_.constFind (call); | 
					
						
							|  |  |  |       if (p != m_->last_uploaded_.end ()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           return p.value ().daysTo (QDate::currentDate ()) <= m_->age_constraint_; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2018-10-01 12:37:52 +01:00
										 |  |  |     } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } |