mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-11-04 05:50:31 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			197 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "LotWUsers.hpp"
 | 
						|
 | 
						|
#include <future>
 | 
						|
#include <chrono>
 | 
						|
 | 
						|
#include <QHash>
 | 
						|
#include <QString>
 | 
						|
#include <QDate>
 | 
						|
#include <QFile>
 | 
						|
#include <QTextStream>
 | 
						|
#include <QDir>
 | 
						|
#include <QFileInfo>
 | 
						|
#include <QPointer>
 | 
						|
#include <QSaveFile>
 | 
						|
#include <QUrl>
 | 
						|
#include <QNetworkAccessManager>
 | 
						|
#include <QNetworkReply>
 | 
						|
#include <QDebug>
 | 
						|
#include "qt_helpers.hpp"
 | 
						|
#include "Logger.hpp"
 | 
						|
#include "FileDownload.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>;
 | 
						|
}
 | 
						|
 | 
						|
class LotWUsers::impl final
 | 
						|
  : public QObject
 | 
						|
{
 | 
						|
  Q_OBJECT
 | 
						|
 | 
						|
public:
 | 
						|
  impl (LotWUsers * self, QNetworkAccessManager * network_manager)
 | 
						|
    : self_ {self}
 | 
						|
    , network_manager_ {network_manager}
 | 
						|
    , url_valid_ {false}
 | 
						|
    , redirect_count_ {0}
 | 
						|
    , age_constraint_ {365}
 | 
						|
    , connected_ {false}
 | 
						|
  {
 | 
						|
  }
 | 
						|
 | 
						|
  void load (QString const& url, bool fetch, bool forced_fetch)
 | 
						|
  {
 | 
						|
    abort ();                   // abort any active download
 | 
						|
    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_.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
 | 
						|
      {
 | 
						|
        if (exists)
 | 
						|
          {
 | 
						|
            // load the database asynchronously
 | 
						|
            future_load_ = std::async (std::launch::async, &LotWUsers::impl::load_dictionary, this, csv_file_name);
 | 
						|
          }
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  void abort ()
 | 
						|
  {
 | 
						|
    lotw_downloader_.abort();
 | 
						|
  }
 | 
						|
 | 
						|
  // 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_dictionary (QString const& lotw_csv_file)
 | 
						|
  {
 | 
						|
    dictionary result;
 | 
						|
    QFile f {lotw_csv_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");
 | 
						|
          }
 | 
						|
      }
 | 
						|
    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;
 | 
						|
  }
 | 
						|
 | 
						|
  LotWUsers * self_;
 | 
						|
  QNetworkAccessManager * network_manager_;
 | 
						|
  QSaveFile csv_file_;
 | 
						|
  bool url_valid_;
 | 
						|
  QUrl current_url_;            // may be a redirect
 | 
						|
  int redirect_count_;
 | 
						|
  QPointer<QNetworkReply> reply_;
 | 
						|
  std::future<dictionary> future_load_;
 | 
						|
  dictionary last_uploaded_;
 | 
						|
  qint64 age_constraint_;       // days
 | 
						|
  FileDownload lotw_downloader_;
 | 
						|
  bool connected_;
 | 
						|
};
 | 
						|
 | 
						|
#include "LotWUsers.moc"
 | 
						|
 | 
						|
LotWUsers::LotWUsers (QNetworkAccessManager * network_manager, QObject * parent)
 | 
						|
  : QObject {parent}
 | 
						|
  , m_ {this, network_manager}
 | 
						|
{
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
LotWUsers::~LotWUsers ()
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
void LotWUsers::set_local_file_path (QString const& path)
 | 
						|
{
 | 
						|
  m_->csv_file_.setFileName (path);
 | 
						|
}
 | 
						|
 | 
						|
void LotWUsers::load (QString const& url, bool fetch, bool force_download)
 | 
						|
{
 | 
						|
  m_->load (url, fetch, force_download);
 | 
						|
}
 | 
						|
 | 
						|
void LotWUsers::set_age_constraint (qint64 uploaded_since_days)
 | 
						|
{
 | 
						|
  m_->age_constraint_ = uploaded_since_days;
 | 
						|
}
 | 
						|
 | 
						|
bool LotWUsers::user (QString const& call) const
 | 
						|
{
 | 
						|
  // 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}))
 | 
						|
    {
 | 
						|
      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 ());
 | 
						|
        }
 | 
						|
      Q_EMIT load_finished ();
 | 
						|
    }
 | 
						|
  if (m_->last_uploaded_.size ())
 | 
						|
    {
 | 
						|
      auto p = m_->last_uploaded_.constFind (call);
 | 
						|
      if (p != m_->last_uploaded_.end ())
 | 
						|
        {
 | 
						|
          return p.value ().daysTo (QDate::currentDate ()) <= m_->age_constraint_;
 | 
						|
        }
 | 
						|
    }
 | 
						|
  return false;
 | 
						|
}
 |