swift
threadedreader.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "core/threadedreader.h"
5 
6 #include <QCoreApplication>
7 #include <QMetaObject>
8 #include <QReadLocker>
9 #include <QThread>
10 #include <QTimer>
11 #include <QWriteLocker>
12 
13 #include "core/application.h"
14 #include "misc/logmessage.h"
16 #include "misc/threadutils.h"
17 #include "misc/verify.h"
18 
19 using namespace swift::misc;
20 using namespace swift::misc::network;
21 using namespace swift::core::vatsim;
22 
23 namespace swift::core
24 {
25  const QStringList &CThreadedReader::getLogCategories()
26  {
27  static const QStringList cats { swift::misc::CLogCategories::worker() };
28  return cats;
29  }
30 
31  CThreadedReader::CThreadedReader(QObject *owner, const QString &name) : CContinuousWorker(owner, name)
32  {
33  connect(&m_updateTimer, &QTimer::timeout, this, &CThreadedReader::doWork);
34  m_updateTimer.setSingleShot(true);
35  }
36 
38 
39  qint64 CThreadedReader::lastModifiedMsSinceEpoch(QNetworkReply *nwReply) const
40  {
41  return CNetworkUtils::lastModifiedMsSinceEpoch(nwReply);
42  }
43 
45  {
46  QReadLocker lock(&m_lock);
47  return m_updateTimestamp;
48  }
49 
50  void CThreadedReader::setUpdateTimestamp(const QDateTime &updateTimestamp)
51  {
52  QWriteLocker lock(&m_lock);
53  m_updateTimestamp = updateTimestamp;
54  }
55 
56  bool CThreadedReader::updatedWithinLastMs(qint64 timeLastMs)
57  {
58  QDateTime dt(getUpdateTimestamp());
59  if (dt.isNull() || !dt.isValid()) { return false; }
60  qint64 delta = QDateTime::currentMSecsSinceEpoch() - dt.toMSecsSinceEpoch();
61  return delta <= timeLastMs;
62  }
63 
65  {
66  Q_ASSERT(m_initialTime > 0);
67  QTimer::singleShot(m_initialTime, this, [=] { this->doWork(); });
68  }
69 
71 
73  {
74  QReadLocker l(&m_lock);
75  return m_urlReadLog.hasPending();
76  }
77 
79  {
80  QReadLocker l(&m_lock);
81  return m_urlReadLog;
82  }
83 
84  bool CThreadedReader::didContentChange(const QString &content, int startPosition)
85  {
86  size_t oldHash = 0;
87  {
88  QReadLocker rl(&m_lock);
89  oldHash = m_contentHash;
90  }
91  size_t newHash = qHash(startPosition < 0 ? content : content.mid(startPosition));
92  if (oldHash == newHash) { return false; }
93  {
94  QWriteLocker wl(&m_lock);
95  m_contentHash = newHash;
96  }
97  return true;
98  }
99 
100  bool CThreadedReader::isMarkedAsFailed() const { return m_markedAsFailed; }
101 
102  void CThreadedReader::setMarkedAsFailed(bool failed) { m_markedAsFailed = failed; }
103 
105  {
106  QReadLocker rl(&m_lock);
107  return m_urlReadLog;
108  }
109 
111  {
112  Q_ASSERT_X(QCoreApplication::instance()->thread() != QThread::currentThread(), Q_FUNC_INFO,
113  "Needs to run in own thread");
114  Q_ASSERT_X(this->thread() == QThread::currentThread(), Q_FUNC_INFO, "Wrong object thread");
115  }
116 
117  void CThreadedReader::setInitialAndPeriodicTime(int initialTime, int periodicTime)
118  {
119  m_initialTime = initialTime;
120  m_periodicTime = periodicTime;
121 
122  // if timer is active start with delta time
123  // remark: will be reset in doWork
124  if (m_updateTimer.isActive())
125  {
126  const int oldPeriodicTime = m_updateTimer.interval();
127  const int delta = m_periodicTime - oldPeriodicTime + m_updateTimer.remainingTime();
128  m_updateTimer.start(qMax(delta, 0));
129  }
130  }
131 
132  void CThreadedReader::doWork()
133  {
134  if (!doWorkCheck()) { return; }
135  this->doWorkImpl();
136  Q_ASSERT(m_periodicTime > 0);
137  m_updateTimer.start(m_periodicTime); // restart
138  }
139 
141  {
142  // sApp->hasWebDataServices() cannot be used, as some readers are already used during init phase
143  if (!this->isEnabled()) { return false; }
144 
145  // MS 2019-02-23 isAbandoned() check only makes sense when called by worker thread (T541)
146  if (CThreadUtils::isInThisThread(this) && this->isAbandoned()) { return false; }
147 
148  if (!m_unitTest && (!sApp || sApp->isShuttingDown())) { return false; }
149  return true;
150  }
151 
152  QNetworkReply *CThreadedReader::getFromNetworkAndLog(const CUrl &url, const CSlot<void(QNetworkReply *)> &callback)
153  {
154  QWriteLocker wl(&m_lock);
155  const CUrlLogList outdatedPendingUrls = m_urlReadLog.findOutdatedPending(OutdatedPendingCallMs);
156  if (!outdatedPendingUrls.isEmpty())
157  {
158  CLogMessage(this).warning(u"Detected outdated reader pending calls: '%1'")
159  << outdatedPendingUrls.toQString(true);
160  m_urlReadLog.removeOlderThanNowMinusOffset(OutdatedPendingCallMs); // clean up
161  }
162 
163  const int id = m_urlReadLog.addPendingUrl(url);
164  wl.unlock();
165 
166  // returned QNetworkReply normally nullptr since QAM is in different thread
167  QNetworkReply *nr = sApp->getFromNetwork(url, id, callback, { this, &CThreadedReader::networkReplyProgress });
168  return nr;
169  }
170 
171  QPair<qint64, qint64> CThreadedReader::getNetworkReplyBytes() const
172  {
173  return QPair<qint64, qint64>(m_networkReplyCurrent, m_networkReplyNax);
174  }
175 
176  void CThreadedReader::networkReplyProgress(int logId, qint64 current, qint64 max, const QUrl &url)
177  {
178  // max can be -1 if file size is not available
179  m_networkReplyProgress = (current > 0 && max > 0) ? static_cast<int>((current * 100) / max) : -1;
180  m_networkReplyCurrent = current;
181  m_networkReplyNax = max;
182 
183  Q_UNUSED(url);
184  Q_UNUSED(logId);
185  }
186 
187  void CThreadedReader::logNetworkReplyReceived(QNetworkReply *reply)
188  {
189  if (!reply) { return; }
190  QWriteLocker wl(&m_lock);
191  m_urlReadLog.markAsReceived(reply, reply->error() == QNetworkReply::NoError);
192  }
193 
194  void CThreadedReader::logInconsistentData(const CStatusMessage &msg, const char *funcInfo)
195  {
196  if (msg.isEmpty()) { return; }
197  CStatusMessage logMsg(msg);
199  if (funcInfo)
200  {
201  const QByteArray m(logMsg.getMessage().toLatin1());
202  SWIFT_AUDIT_X(false, funcInfo, m.constData());
203  }
205  }
206 } // namespace swift::core
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
QNetworkReply * getFromNetwork(const swift::misc::network::CUrl &url, const CallbackSlot &callback, int maxRedirects=DefaultMaxRedirects)
Request to get network reply.
bool isShuttingDown() const
Is application shutting down?
std::atomic_llong m_networkReplyCurrent
current bytes
QDateTime getUpdateTimestamp() const
Thread safe, get update timestamp.
void pauseReader()
Pauses the reader.
QPair< qint64, qint64 > getNetworkReplyBytes() const
Max./current bytes.
void setUpdateTimestamp(const QDateTime &updateTimestamp=QDateTime::currentDateTimeUtc())
Thread safe, set update timestamp.
virtual void networkReplyProgress(int logId, qint64 current, qint64 max, const QUrl &url)
Network request progress.
static void logInconsistentData(const swift::misc::CStatusMessage &msg, const char *funcInfo=nullptr)
Use this to log inconsistent data.
virtual ~CThreadedReader()
Destructor.
swift::misc::network::CUrlLogList getReadLog() const
Get the read log.
void setMarkedAsFailed(bool failed)
Set marker for read failed.
std::atomic_int m_networkReplyProgress
Progress percentage 0...100.
bool isMarkedAsFailed() const
Is marked as read failed.
void threadAssertCheck() const
Make sure everthing runs correctly in own thread.
void logNetworkReplyReceived(QNetworkReply *reply)
Network reply received, mark in m_urlReadLog.
QReadWriteLock m_lock
lock which can be used from the derived classes
bool hasPendingUrls() const
Has pending URLs?
bool didContentChange(const QString &content, int startPosition=-1)
Stores new content hash and returns if content changed (based on hash value.
swift::misc::network::CUrlLogList getUrlLogList() const
Get the URL log list.
bool updatedWithinLastMs(qint64 timeLastMs)
Was setup read within last xx milliseconds.
void startReader()
Starts the reader.
std::atomic_llong m_networkReplyNax
max bytes
virtual void doWorkImpl()
This method does the actual work in the derived class.
QNetworkReply * getFromNetworkAndLog(const swift::misc::network::CUrl &url, const swift::misc::CSlot< void(QNetworkReply *)> &callback)
Get request from network, and log with m_urlReadLog.
void setInitialAndPeriodicTime(int initialTime, int periodicTime)
Set initial and periodic times.
bool doWorkCheck() const
Still enabled etc.?
qint64 lastModifiedMsSinceEpoch(QNetworkReply *nwReply) const
When was reply last modified, -1 if N/A.
Base class for a long-lived worker object which lives in its own thread.
Definition: worker.h:275
bool isEnabled() const
Enabled (running)?
Definition: worker.h:300
QTimer m_updateTimer
timer which can be used by implementing classes
Definition: worker.h:333
static const QString & worker()
Background task.
static const QString & dataInconsistency()
Data inconsistency.
Class for emitting a log message.
Definition: logmessage.h:27
static void preformatted(const CStatusMessage &statusMessage)
Sends a verbatim, preformatted message to the log.
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
bool isEmpty() const
Message empty.
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Callable wrapper for a member function with function signature F.
Definition: slot.h:62
Streamable status message, e.g.
QString getMessage() const
Message.
void addCategory(const CLogCategory &category)
Add category, avoids duplicates.
static bool isInThisThread(const QObject *toBeTested)
Is the current thread the object's thread?
Definition: threadutils.cpp:16
bool isAbandoned() const
For the task to check whether it can finish early.
Definition: worker.cpp:153
int removeOlderThanNowMinusOffset(qint64 offsetMs)
Remove objects older than seconds.
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
Value object encapsulating information of a location, kind of simplified CValueObject compliant versi...
Definition: url.h:27
Value object encapsulating a list of voice rooms.
Definition: urlloglist.h:26
bool markAsReceived(int id, bool success)
Mark as received.
Definition: urlloglist.cpp:68
CUrlLogList findOutdatedPending(int outdatedOffsetMs) const
Find outdated pending log entries.
Definition: urlloglist.cpp:31
bool hasPending() const
Any pending calls.
Definition: urlloglist.cpp:48
int addPendingUrl(const CUrl &url, int maxNumber=10)
Add a pending URL.
Definition: urlloglist.cpp:14
size_t qHash(const std::string &key, uint seed)
std::string qHash
Definition: metaclass.h:86
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
Free functions in swift::misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
#define SWIFT_AUDIT_X(COND, WHERE, WHAT)
A weaker kind of verify.
Definition: verify.h:38