File: LotWUsers.cpp

package info (click to toggle)
wsjtx 2.7.0%2Brepack-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 70,440 kB
  • sloc: cpp: 75,379; f90: 46,460; python: 27,241; ansic: 13,367; fortran: 2,382; makefile: 197; sh: 133
file content (196 lines) | stat: -rwxr-xr-x 5,589 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#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;
}