MythTV  master
netstream.cpp
Go to the documentation of this file.
1 /* Network stream
2  * Copyright 2011 Lawrence Rust <lvr at softsystem dot co dot uk>
3  */
4 #include "netstream.h"
5 
6 // C/C++ lib
7 #include <algorithm>
8 #include <cstddef>
9 #include <cstdio>
10 #include <cinttypes>
11 #include <utility>
12 
13 // Qt
14 #include <QAtomicInt>
15 #include <QCoreApplication>
16 #include <QDesktopServices>
17 #include <QElapsedTimer>
18 #include <QEvent>
19 #include <QFile>
20 #include <QMetaType> // qRegisterMetaType
21 #include <QMutexLocker>
22 #include <QNetworkAccessManager>
23 #include <QNetworkDiskCache>
24 #include <QNetworkInterface>
25 #include <QNetworkProxy>
26 #include <QNetworkReply>
27 #include <QNetworkRequest>
28 #include <QThread>
29 #include <QTimeZone>
30 #include <QUrl>
31 #ifndef QT_NO_OPENSSL
32 #include <QSslConfiguration>
33 #include <QSslError>
34 #include <QSslSocket>
35 #include <QSslKey>
36 #endif
37 
38 // Myth
40 #include "libmythbase/mythdirs.h"
42 
43 /*
44  * Constants
45  */
46 static const QString LOC { QStringLiteral("[netstream] ") };
47 
48 
49 /*
50  * Private data
51  */
52 static QAtomicInt s_nRequest(1); // Unique NetStream request ID
53 static QMutex s_mtx; // Guard local static data e.g. NAMThread singleton
54 static constexpr qint64 kMaxBuffer = 4LL * 1024 * 1024L; // 0= unlimited, 1MB => 4secs @ 1.5Mbps
55 
56 
57 /*
58  * Private types
59  */
60 // Custom event posted to NAMThread
61 class NetStreamRequest : public QEvent
62 {
63 public:
64  static const QEvent::Type kType = QEvent::User;
65 
66  NetStreamRequest(int id, const QNetworkRequest &req) :
67  QEvent(kType),
68  m_id(id),
69  m_req(req)
70  { }
71 
72  const int m_id;
73  const QNetworkRequest m_req;
74  volatile bool m_bCancelled { false };
75 };
76 
77 class NetStreamAbort : public QEvent
78 {
79 public:
80  static const QEvent::Type kType = static_cast< QEvent::Type >(QEvent::User + 1);
81 
82  NetStreamAbort(int id, QNetworkReply *reply) :
83  QEvent(kType),
84  m_id(id),
85  m_reply(reply)
86  { }
87 
88  const int m_id;
89  QNetworkReply * const m_reply;
90 };
91 
92 
96 NetStream::NetStream(const QUrl &url, EMode mode /*= kPreferCache*/,
97  QByteArray cert) :
98  m_id(s_nRequest.fetchAndAddRelaxed(1)),
99  m_url(url),
100  m_cert(std::move(cert))
101 {
102  setObjectName("NetStream " + url.toString());
103 
104  QNetworkRequest::CacheLoadControl attr {QNetworkRequest::PreferNetwork};
105  if (mode == kAlwaysCache)
106  attr = QNetworkRequest::AlwaysCache;
107  else if (mode == kPreferCache)
108  attr = QNetworkRequest::PreferCache;
109  else if (mode == kNeverCache)
110  attr = QNetworkRequest::AlwaysNetwork;
111  m_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, attr);
112 
113  // Receive requestStarted signals from NAMThread when it processes a NetStreamRequest
115  this, &NetStream::slotRequestStarted, Qt::DirectConnection );
116 
117  QMutexLocker locker(&m_mutex);
118 
119  if (Request(url))
120  m_state = kPending;
121 }
122 
123 // virtual
125 {
126  Abort();
127 
128  (NAMThread::manager()).disconnect(this);
129 
130  QMutexLocker locker(&m_mutex);
131 
132  if (m_reply)
133  {
134  m_reply->disconnect(this);
135  m_reply->deleteLater();
136  }
137 }
138 
139 static inline QString Source(const QNetworkRequest &request)
140 {
141  switch (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt())
142  {
143  case QNetworkRequest::AlwaysCache: return "cache";
144  case QNetworkRequest::PreferCache: return "cache-preferred";
145  case QNetworkRequest::PreferNetwork: return "net-preferred";
146  case QNetworkRequest::AlwaysNetwork: return "net";
147  }
148  return "unknown";
149 }
150 
151 static inline QString Source(const QNetworkReply* reply)
152 {
153  return reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool() ?
154  "cache" : "host";
155 }
156 
157 // Send request to the network manager
158 // Caller must hold m_mutex
159 bool NetStream::Request(const QUrl& url)
160 {
161  if (!IsSupported(url))
162  {
163  LOG(VB_GENERAL, LOG_WARNING, LOC +
164  QString("(%1) Request unsupported URL: %2")
165  .arg(m_id).arg(url.toString()) );
166  return false;
167  }
168 
169  if (m_pending)
170  {
171  // Cancel the pending request
172  m_pending->m_bCancelled = true;
173  m_pending = nullptr;
174  }
175 
176  if (m_reply)
177  {
178  // Abort the current reply
179  // NB the abort method appears to only work if called from NAMThread
180  m_reply->disconnect(this);
182  // NAMthread will delete the reply
183  m_reply = nullptr;
184  }
185 
186  m_request.setUrl(url);
187 
188  const QByteArray ua("User-Agent");
189  if (!m_request.hasRawHeader(ua))
190  m_request.setRawHeader(ua, "UK-MHEG/2 MYT001/001 MHGGNU/001");
191 
192  if (m_pos > 0 || m_size >= 0)
193  m_request.setRawHeader("Range", QString("bytes=%1-").arg(m_pos).toLatin1());
194 
195 #ifndef QT_NO_OPENSSL
196  if (m_request.url().scheme() == "https")
197  {
198  QSslConfiguration ssl(QSslConfiguration::defaultConfiguration());
199 
200  QList<QSslCertificate> clist;
201  if (!m_cert.isEmpty())
202  {
203  clist = QSslCertificate::fromData(m_cert, QSsl::Der);
204  if (clist.isEmpty())
205  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Invalid certificate: %1")
206  .arg(m_cert.toPercentEncoding().constData()) );
207  }
208 
209  if (clist.isEmpty())
210  {
211  // The BBC servers use a self certified cert so don't verify it
212  ssl.setPeerVerifyMode(QSslSocket::VerifyNone);
213  }
214  else
215  {
216  ssl.setCaCertificates(clist);
217  }
218 
219  // We need to provide a client certificate for the BBC, See:
220  // openssl s_client -state -prexit -connect securegate.iplayer.bbc.co.uk:443
221  // for a list of accepted certificates
222  QString fname = gCoreContext->GetSetting("MhegClientCert", "");
223  if (!fname.isEmpty())
224  {
225  QFile f1(QFile::exists(fname) ? fname : GetShareDir() + fname);
226  if (f1.open(QIODevice::ReadOnly))
227  {
228  QSslCertificate cert(&f1, QSsl::Pem);
229  if (!cert.isNull())
230  ssl.setLocalCertificate(cert);
231  else
232  LOG(VB_GENERAL, LOG_WARNING, LOC +
233  QString("'%1' is an invalid certificate").arg(f1.fileName()) );
234  }
235  else
236  {
237  LOG(VB_GENERAL, LOG_WARNING, LOC +
238  QString("Opening client certificate '%1': %2")
239  .arg(f1.fileName(), f1.errorString()) );
240  }
241 
242  // Get the private key
243  fname = gCoreContext->GetSetting("MhegClientKey", "");
244  if (!fname.isEmpty())
245  {
246  QFile f2(QFile::exists(fname) ? fname : GetShareDir() + fname);
247  if (f2.open(QIODevice::ReadOnly))
248  {
249  QSslKey key(&f2, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey,
250  gCoreContext->GetSetting("MhegClientKeyPass", "").toLatin1());
251  if (!key.isNull())
252  ssl.setPrivateKey(key);
253  else
254  LOG(VB_GENERAL, LOG_WARNING, LOC +
255  QString("'%1' is an invalid key").arg(f2.fileName()) );
256  }
257  else
258  {
259  LOG(VB_GENERAL, LOG_WARNING, LOC +
260  QString("Opening private key '%1': %2")
261  .arg(f2.fileName(), f2.errorString()) );
262  }
263  }
264  }
265 
266  m_request.setSslConfiguration(ssl);
267  }
268 #endif
269 
270  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Request %2 bytes=%3- from %4")
271  .arg(m_id).arg(m_request.url().toString())
272  .arg(m_pos).arg(Source(m_request)) );
275  return true;
276 }
277 
278 // signal from NAMThread manager that a request has been started
279 void NetStream::slotRequestStarted(int id, QNetworkReply *reply)
280 {
281  QMutexLocker locker(&m_mutex);
282 
283  if (m_id != id)
284  return;
285 
286  m_pending = nullptr; // Event is no longer valid
287 
288  if (!m_reply)
289  {
290  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Started 0x%2")
291  .arg(m_id).arg(quintptr(reply),0,16) );
292 
293  m_reply = reply;
294  m_state = kStarted;
295 
296  reply->setReadBufferSize(kMaxBuffer);
297 
298  // NB The following signals must be Qt::DirectConnection 'cos this slot
299  // was connected Qt::DirectConnection so the current thread is NAMThread
300 
301  // QNetworkReply signals
302  connect(reply, &QNetworkReply::finished, this, &NetStream::slotFinished, Qt::DirectConnection );
303 #ifndef QT_NO_OPENSSL
304  connect(reply, &QNetworkReply::sslErrors, this,
305  &NetStream::slotSslErrors, Qt::DirectConnection );
306 #endif
307  // QIODevice signals
308  connect(reply, &QIODevice::readyRead, this, &NetStream::slotReadyRead, Qt::DirectConnection );
309  }
310  else
311  {
312  LOG(VB_GENERAL, LOG_ERR, LOC +
313  QString("(%1) Started but m_reply not NULL").arg(m_id));
314  }
315 }
316 
317 static qlonglong inline ContentLength(const QNetworkReply *reply)
318 {
319  bool ok = false;
320  qlonglong len = reply->header(QNetworkRequest::ContentLengthHeader)
321  .toLongLong(&ok);
322  return ok ? len : -1;
323 }
324 
325 static qlonglong inline ContentRange(const QNetworkReply *reply,
326  qulonglong &first, qulonglong &last)
327 {
328  QByteArray range = reply->rawHeader("Content-Range");
329  if (range.isEmpty())
330  return -1;
331 
332  // See RFC 2616 14.16: 'bytes begin-end/size'
333  qulonglong len = 0;
334  const char *fmt = " bytes %20" SCNd64 " - %20" SCNd64 " / %20" SCNd64;
335  if (3 != std::sscanf(range.constData(), fmt, &first, &last, &len))
336  {
337  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Invalid Content-Range:'%1'")
338  .arg(range.constData()) );
339  return -1;
340  }
341 
342  return static_cast<qlonglong>(len);
343 }
344 
345 #if 0
346 static bool inline RequestRange(const QNetworkRequest &request,
347  qlonglong &first, qlonglong &last)
348 {
349  first = last = -1;
350 
351  QByteArray range = request.rawHeader("Range");
352  if (range.isEmpty())
353  return false;
354 
355  if (1 > std::sscanf(range.constData(), " bytes %20lld - %20lld", &first, &last))
356  {
357  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Invalid Range:'%1'")
358  .arg(range.constData()) );
359  return false;
360  }
361 
362  return true;
363 }
364 #endif
365 
366 // signal from QNetworkReply
368 {
369  QMutexLocker locker(&m_mutex);
370 
371  if (m_reply)
372  {
373  qint64 avail = m_reply->bytesAvailable();
374  LOG(VB_FILE, (avail <= 2 * kMaxBuffer) ? LOG_DEBUG :
375  (avail <= 4 * kMaxBuffer) ? LOG_INFO : LOG_WARNING,
376  LOC + QString("(%1) Ready 0x%2, %3 bytes available").arg(m_id)
377  .arg(quintptr(m_reply),0,16).arg(avail) );
378 
379  if (m_size < 0 || m_state < kReady)
380  {
381  qulonglong first = 0;
382  qulonglong last = 0;
383  qlonglong len = ContentRange(m_reply, first, last);
384  if (len >= 0)
385  {
386  m_size = len;
387  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Ready 0x%2, range %3-%4/%5")
388  .arg(m_id).arg(quintptr(m_reply),0,16).arg(first).arg(last).arg(len) );
389  }
390  else
391  {
393  if (m_state < kReady || m_size >= 0)
394  {
395  LOG(VB_FILE, LOG_INFO, LOC +
396  QString("(%1) Ready 0x%2, content length %3")
397  .arg(m_id).arg(quintptr(m_reply),0,16).arg(m_size) );
398  }
399  }
400  }
401 
402  m_state = std::max(m_state, kReady);
403 
404  locker.unlock();
405  emit ReadyRead(this);
406  locker.relock();
407 
408  m_ready.wakeAll();
409  }
410  else
411  {
412  LOG(VB_GENERAL, LOG_ERR, LOC +
413  QString("(%1) ReadyRead but m_reply = NULL").arg(m_id));
414  }
415 }
416 
417 // signal from QNetworkReply
419 {
420  QMutexLocker locker(&m_mutex);
421 
422  if (m_reply)
423  {
424  QNetworkReply::NetworkError error = m_reply->error();
425  if (QNetworkReply::NoError == error)
426  {
427  // Check for a re-direct
428  QUrl url = m_reply->attribute(
429  QNetworkRequest::RedirectionTargetAttribute).toUrl();
430  if (!url.isValid())
431  {
432  m_state = kFinished;
433  }
434  else if (m_nRedirections++ > 0)
435  {
436  LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Too many redirections")
437  .arg(m_id));
438  m_state = kFinished;
439  }
440  else
441  {
442  url = m_request.url().resolved(url);
443  if (url == m_request.url())
444  {
445  LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Redirection loop to %2")
446  .arg(m_id).arg(url.toString()) );
447  m_state = kFinished;
448  }
449  else
450  {
451  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Redirecting").arg(m_id));
452  m_state = Request(url) ? kPending : kFinished;
453  }
454  }
455  }
456  else
457  {
458  LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1): %2")
459  .arg(m_id).arg(m_reply->errorString()) );
460  m_state = kFinished;
461  }
462 
463  if (m_state == kFinished)
464  {
465  if (m_size < 0)
466  m_size = m_pos + m_reply->size();
467 
468  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Finished 0x%2 %3/%4 bytes from %5")
469  .arg(m_id).arg(quintptr(m_reply),0,16).arg(m_pos).arg(m_size).arg(Source(m_reply)) );
470 
471  locker.unlock();
472  emit Finished(this);
473  locker.relock();
474 
475  m_finished.wakeAll();
476  }
477  }
478  else
479  {
480  LOG(VB_GENERAL, LOG_ERR, LOC + QString("(%1) Finished but m_reply = NULL")
481  .arg(m_id));
482  }
483 }
484 
485 #ifndef QT_NO_OPENSSL
486 // signal from QNetworkReply
487 void NetStream::slotSslErrors(const QList<QSslError> &errors)
488 {
489  QMutexLocker locker(&m_mutex);
490 
491  if (m_reply)
492  {
493  bool bIgnore = true;
494  for (const auto& e : std::as_const(errors))
495  {
496  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL error %2: ")
497  .arg(m_id).arg(e.error()) + e.errorString() );
498  switch (e.error())
499  {
500 #if 1 // The BBC use a self certified cert
501  case QSslError::SelfSignedCertificateInChain:
502  break;
503 #endif
504  default:
505  bIgnore = false;
506  break;
507  }
508  }
509 
510  if (bIgnore)
511  {
512  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL errors ignored").arg(m_id));
513  m_reply->ignoreSslErrors(errors);
514  }
515  }
516  else
517  {
518  LOG(VB_GENERAL, LOG_ERR, LOC +
519  QString("(%1) SSL error but m_reply = NULL").arg(m_id) );
520  }
521 }
522 #endif
523 
524 
528 // static
529 bool NetStream::IsSupported(const QUrl &url)
530 {
531  return url.isValid() &&
532  (url.scheme() == "http" || url.scheme() == "https") &&
533  !url.authority().isEmpty() &&
534  !url.path().isEmpty();
535 }
536 
537 bool NetStream::IsOpen() const
538 {
539  QMutexLocker locker(&m_mutex);
540  return m_state > kClosed;
541 }
542 
544 {
545  QMutexLocker locker(&m_mutex);
546 
547  if (m_pending)
548  {
549  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Cancelled").arg(m_id) );
550  m_pending->m_bCancelled = true;
551  m_pending = nullptr;
552  }
553 
554  if (m_reply)
555  {
556  if (m_state >= kStarted && m_state < kFinished)
557  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Abort 0x%2")
558  .arg(m_id).arg(quintptr(m_reply),0,16) );
559 
561  // NAMthread will delete the reply
562  m_reply = nullptr;
563  }
564 
565  m_state = kFinished;
566 }
567 
568 int NetStream::safe_read(void *data, unsigned sz, unsigned millisecs /* = 0 */)
569 {
570  QElapsedTimer t; t.start();
571  QMutexLocker locker(&m_mutex);
572 
573  if (m_size >= 0 && m_pos >= m_size)
574  return 0; // EOF
575 
576  while (m_state < kFinished && (!m_reply || m_reply->bytesAvailable() < sz))
577  {
578  unsigned elapsed = t.elapsed();
579  if (elapsed >= millisecs)
580  break;
581  m_ready.wait(&m_mutex, millisecs - elapsed);
582  }
583 
584  locker.unlock();
585  QMutexLocker lockNAM(NAMThread::GetMutex());
586  locker.relock();
587  if (!m_reply)
588  return -1;
589 
590  qint64 avail = m_reply->read(reinterpret_cast< char* >(data), sz);
591  if (avail <= 0)
592  return m_state >= kFinished ? 0 : -1; // 0= EOF
593 
594  LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) safe_read @ %4 => %2/%3, %5 mS")
595  .arg(m_id).arg(avail).arg(sz).arg(m_pos).arg(t.elapsed()) );
596  m_pos += avail;
597  return (int)avail;
598 }
599 
600 qlonglong NetStream::Seek(qlonglong pos)
601 {
602  QMutexLocker locker(&m_mutex);
603 
604  if (pos == m_pos)
605  return pos;
606 
607  if (pos < 0 || (m_size >= 0 && pos > m_size))
608  {
609  LOG(VB_GENERAL, LOG_ERR, LOC +
610  QString("(%1) Seek(%2) out of range [0..%3]")
611  .arg(m_id).arg(pos).arg(m_size) );
612  return -1;
613  }
614 
615  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Seek(%2) curr %3 end %4")
616  .arg(m_id).arg(pos).arg(m_pos).arg(m_size) );
617  m_pos = pos;
618  return Request(m_request.url()) ? m_pos : -1;
619 }
620 
621 qlonglong NetStream::GetReadPosition() const
622 {
623  QMutexLocker locker(&m_mutex);
624 
625  return m_pos;
626 }
627 
628 qlonglong NetStream::GetSize() const
629 {
630  QMutexLocker locker(&m_mutex);
631 
632  return m_size;
633 }
634 
635 
639 bool NetStream::WaitTillReady(std::chrono::milliseconds timeout)
640 {
641  QMutexLocker locker(&m_mutex);
642 
643  QElapsedTimer t; t.start();
644  while (m_state < kReady)
645  {
646  auto elapsed = std::chrono::milliseconds(t.elapsed());
647  if (elapsed > timeout)
648  return false;
649 
650  m_ready.wait(&m_mutex, (timeout - elapsed).count());
651  }
652 
653  return true;
654 }
655 
656 bool NetStream::WaitTillFinished(std::chrono::milliseconds timeout)
657 {
658  QMutexLocker locker(&m_mutex);
659 
660  QElapsedTimer t; t.start();
661  while (m_state < kFinished)
662  {
663  auto elapsed = std::chrono::milliseconds(t.elapsed());
664  if (elapsed > timeout)
665  return false;
666 
667  m_finished.wait(&m_mutex, (timeout - elapsed).count());
668  }
669 
670  return true;
671 }
672 
673 QNetworkReply::NetworkError NetStream::GetError() const
674 {
675  QMutexLocker locker(&m_mutex);
676  return !m_reply ? QNetworkReply::OperationCanceledError : m_reply->error();
677 }
678 
680 {
681  QMutexLocker locker(&m_mutex);
682  return !m_reply ? "Operation cancelled" : m_reply->errorString();
683 }
684 
685 qlonglong NetStream::BytesAvailable() const
686 {
687  QMutexLocker locker(&m_mutex);
688  return m_reply ? m_reply->bytesAvailable() : 0;
689 }
690 
691 QByteArray NetStream::ReadAll()
692 {
693  QMutexLocker locker(&m_mutex);
694 
695  if (!m_reply)
696  return nullptr;
697 
698  QByteArray data = m_reply->readAll();
699  m_pos += data.size();
700  return data;
701 }
702 
707 {
708  QMutexLocker locker(&m_mutex);
709  return m_state >= kStarted;
710 }
711 
712 bool NetStream::isReady() const
713 {
714  QMutexLocker locker(&m_mutex);
715  return m_state >= kReady;
716 }
717 
719 {
720  QMutexLocker locker(&m_mutex);
721  return m_state >= kFinished;
722 }
723 
727 // static
729 {
730  return NAMThread::isAvailable();
731 }
732 
733 // Time when URI was last written to cache or invalid if not cached.
734 // static
735 QDateTime NetStream::GetLastModified(const QUrl &url)
736 {
737  return NAMThread::GetLastModified(url);
738 }
739 
740 
744 //static
746 {
747  QMutexLocker locker(&s_mtx);
748 
749  // Singleton
750  static NAMThread s_thread;
751  s_thread.start();
752  return s_thread;
753 }
754 
756 {
757  setObjectName("NAMThread");
758 
759 #ifndef QT_NO_OPENSSL
760  // This ought to be done by the Qt lib but isn't in 4.7
761  //Q_DECLARE_METATYPE(QList<QSslError>)
762  qRegisterMetaType< QList<QSslError> >();
763 #endif
764 }
765 
766 // virtual
768 {
769  QMutexLocker locker(&m_mutex);
770  delete m_nam;
771 }
772 
773 // virtual
775 {
776  LOG(VB_FILE, LOG_INFO, LOC + "NAMThread starting");
777 
778  m_nam = new QNetworkAccessManager();
779  m_nam->setObjectName("NetStream NAM");
780 
781  // Setup cache
782  std::unique_ptr<QNetworkDiskCache> cache(new QNetworkDiskCache());
783 
784  cache->setCacheDirectory(GetConfDir() + "/cache/netstream-" +
786 
787  m_nam->setCache(cache.release());
788 
789  // Setup a network proxy e.g. for TOR: socks://localhost:9050
790  // TODO get this from mythdb
791  QString proxy(qEnvironmentVariable("MYTHMHEG_PROXY"));
792  if (!proxy.isEmpty())
793  {
794  QUrl url(proxy, QUrl::TolerantMode);
795  QNetworkProxy::ProxyType type {QNetworkProxy::NoProxy};
796  if (url.scheme().isEmpty()
797  || (url.scheme() == "http")
798  || (url.scheme() == "https"))
799  type = QNetworkProxy::HttpProxy;
800  else if (url.scheme() == "socks")
801  type = QNetworkProxy::Socks5Proxy;
802  else if (url.scheme() == "cache")
803  type = QNetworkProxy::HttpCachingProxy;
804  else if (url.scheme() == "ftp")
805  type = QNetworkProxy::FtpCachingProxy;
806 
807  if (QNetworkProxy::NoProxy != type)
808  {
809  LOG(VB_GENERAL, LOG_INFO, LOC + "Using proxy: " + proxy);
810  m_nam->setProxy(QNetworkProxy(
811  type, url.host(), url.port(), url.userName(), url.password() ));
812  }
813  else
814  {
815  LOG(VB_MHEG, LOG_ERR, LOC + QString("Unknown proxy type %1")
816  .arg(url.scheme()) );
817  }
818  }
819 
820  // Quit when main app quits
821  connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
822  this, &NAMThread::quit);
823 
824  m_running.release();
825 
826  QMutexLocker lockNAM(&m_mutexNAM);
827  while(!m_bQuit)
828  {
829  // Process NAM events
830  QCoreApplication::processEvents();
831 
832  lockNAM.unlock();
833 
834  QMutexLocker locker(&m_mutex);
835  m_work.wait(&m_mutex, 100);
836 
837  lockNAM.relock();
838 
839  while (!m_workQ.isEmpty())
840  {
841  QScopedPointer< QEvent > ev(m_workQ.dequeue());
842  locker.unlock();
843  NewRequest(ev.data());
844  locker.relock();
845  }
846  }
847 
848  m_running.acquire();
849 
850  delete m_nam;
851  m_nam = nullptr;
852 
853  LOG(VB_FILE, LOG_INFO, LOC + "NAMThread stopped");
854 }
855 
856 // slot
858 {
859  m_bQuit = true;
860  QThread::quit();
861 }
862 
863 void NAMThread::Post(QEvent *event)
864 {
865  QMutexLocker locker(&m_mutex);
866  m_workQ.enqueue(event);
867 }
868 
869 bool NAMThread::NewRequest(QEvent *event)
870 {
871  switch (event->type())
872  {
874  return StartRequest(dynamic_cast< NetStreamRequest* >(event));
875 #pragma GCC diagnostic push
876 #pragma GCC diagnostic ignored "-Wswitch"
878  return AbortRequest(dynamic_cast< NetStreamAbort* >(event));
879 #pragma GCC diagnostic pop
880  default:
881  break;
882  }
883  return false;
884 }
885 
887 {
888  if (!p)
889  {
890  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid NetStreamRequest");
891  return false;
892  }
893 
894  if (!p->m_bCancelled)
895  {
896  QNetworkReply *reply = m_nam->get(p->m_req);
897  LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) StartRequest 0x%2")
898  .arg(p->m_id).arg(quintptr(reply),0,16) );
899  emit requestStarted(p->m_id, reply);
900  }
901  else
902  {
903  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) NetStreamRequest cancelled").arg(p->m_id) );
904  }
905  return true;
906 }
907 
909 {
910  if (!p)
911  {
912  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid NetStreamAbort");
913  return false;
914  }
915 
916  LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) AbortRequest 0x%2").arg(p->m_id)
917  .arg(quintptr(p->m_reply),0,16) );
918  p->m_reply->abort();
919  p->m_reply->disconnect();
920  delete p->m_reply;
921  return true;
922 }
923 
924 // static
926 {
927  auto interfaces = QNetworkInterface::allInterfaces();
928  return std::any_of(interfaces.begin(), interfaces.end(),
929  [](const QNetworkInterface& iface)
930  {
931  auto f = iface.flags();
932  if (f.testFlag(QNetworkInterface::IsLoopBack))
933  return false;
934  return f.testFlag(QNetworkInterface::IsRunning);
935  } );
936 }
937 
938 // Time when URI was last written to cache or invalid if not cached.
939 // static
940 QDateTime NAMThread::GetLastModified(const QUrl &url)
941 {
942  NAMThread &m = manager();
943 
944  QMutexLocker locker(&m.m_mutex);
945 
946  if (!m.m_nam)
947  return {}; // Invalid
948 
949  QAbstractNetworkCache *cache = m.m_nam->cache();
950  if (!cache)
951  return {}; // Invalid
952 
953  QNetworkCacheMetaData meta = cache->metaData(url);
954  if (!meta.isValid())
955  {
956  LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') not in cache")
957  .arg(url.toString()));
958  return {}; // Invalid
959  }
960 
961  // Check if expired
962  QDateTime const now(QDateTime::currentDateTime()); // local time
963  QDateTime expire = meta.expirationDate();
964  if (expire.isValid() && expire.toLocalTime() < now)
965  {
966  LOG(VB_FILE, LOG_INFO, LOC + QString("GetLastModified('%1') past expiration %2")
967  .arg(url.toString(), expire.toString()));
968  return {}; // Invalid
969  }
970 
971  // Get time URI was modified (Last-Modified header) NB this may be invalid
972  QDateTime lastMod = meta.lastModified();
973 
974  QNetworkCacheMetaData::RawHeaderList headers = meta.rawHeaders();
975  for (const auto& h : std::as_const(headers))
976  {
977  // RFC 1123 date format: Thu, 01 Dec 1994 16:00:00 GMT
978  static const QString kSzFormat { "ddd, dd MMM yyyy HH:mm:ss 'GMT'" };
979 
980  QString const first(h.first.toLower());
981  if (first == "cache-control")
982  {
983  QString const second(h.second.toLower());
984  if (second == "no-cache" || second == "no-store")
985  {
986  LOG(VB_FILE, LOG_INFO, LOC +
987  QString("GetLastModified('%1') Cache-Control disabled")
988  .arg(url.toString()) );
989  cache->remove(url);
990  return {}; // Invalid
991  }
992  }
993  else if (first == "date")
994  {
995  QDateTime d = QDateTime::fromString(h.second, kSzFormat);
996  if (!d.isValid())
997  {
998  LOG(VB_GENERAL, LOG_WARNING, LOC +
999  QString("GetLastModified invalid Date header '%1'")
1000  .arg(h.second.constData()));
1001  continue;
1002  }
1003 #if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1004  d.setTimeSpec(Qt::UTC);
1005 #else
1006  d.setTimeZone(QTimeZone(QTimeZone::UTC));
1007 #endif
1008  lastMod = d;
1009  }
1010  }
1011 
1012  LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') last modified %2")
1013  .arg(url.toString(), lastMod.toString()));
1014  return lastMod;
1015 }
1016 
1017 /* End of file */
NetStream::ReadAll
QByteArray ReadAll()
Definition: netstream.cpp:691
Source
static QString Source(const QNetworkRequest &request)
Definition: netstream.cpp:139
NetStream::slotSslErrors
void slotSslErrors(const QList< QSslError > &errors)
Definition: netstream.cpp:487
NAMThread::m_bQuit
volatile bool m_bQuit
Definition: netstream.h:150
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:102
NetStream::m_ready
QWaitCondition m_ready
Definition: netstream.h:107
NetStream::kReady
@ kReady
Definition: netstream.h:100
NetStreamRequest::m_req
const QNetworkRequest m_req
Definition: netstream.cpp:73
NAMThread::m_nam
QNetworkAccessManager * m_nam
Definition: netstream.h:153
error
static void error(const char *str,...)
Definition: vbi.cpp:37
NAMThread::StartRequest
bool StartRequest(NetStreamRequest *p)
Definition: netstream.cpp:886
NetStream::Request
bool Request(const QUrl &url)
Definition: netstream.cpp:159
s_mtx
static QMutex s_mtx
Definition: netstream.cpp:53
NetStream::kAlwaysCache
@ kAlwaysCache
Definition: netstream.h:36
NetStream::isStarted
bool isStarted() const
Asynchronous interface.
Definition: netstream.cpp:706
NetStream::GetLastModified
static QDateTime GetLastModified(const QUrl &url)
Definition: netstream.cpp:735
NAMThread::m_mutexNAM
QRecursiveMutex m_mutexNAM
Definition: netstream.h:152
NetStream::isFinished
bool isFinished() const
Definition: netstream.cpp:718
NAMThread::m_running
QSemaphore m_running
Definition: netstream.h:151
NAMThread::NewRequest
bool NewRequest(QEvent *event)
Definition: netstream.cpp:869
kMaxBuffer
static constexpr qint64 kMaxBuffer
Definition: netstream.cpp:54
ContentLength
static qlonglong ContentLength(const QNetworkReply *reply)
Definition: netstream.cpp:317
xbmcvfs.exists
bool exists(str path)
Definition: xbmcvfs.py:51
NetStream::m_pending
NetStreamRequest * m_pending
Definition: netstream.h:101
NetStream::m_finished
QWaitCondition m_finished
Definition: netstream.h:108
NAMThread::quit
void quit()
Definition: netstream.cpp:857
NAMThread::Post
void Post(QEvent *event)
Definition: netstream.cpp:863
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
s_nRequest
static QAtomicInt s_nRequest(1)
NetStream::m_request
QNetworkRequest m_request
Definition: netstream.h:98
NetStream::m_reply
QNetworkReply * m_reply
Definition: netstream.h:102
NetStream::m_nRedirections
int m_nRedirections
Definition: netstream.h:103
NAMThread::m_workQ
QQueue< QEvent * > m_workQ
Definition: netstream.h:155
mythdirs.h
NetStream::EMode
EMode
Definition: netstream.h:36
NetStream::WaitTillFinished
bool WaitTillFinished(std::chrono::milliseconds timeout)
Definition: netstream.cpp:656
NetStream::m_id
const int m_id
Definition: netstream.h:94
LOC
static const QString LOC
Definition: netstream.cpp:46
NAMThread::PostEvent
static void PostEvent(QEvent *e)
Definition: netstream.h:126
NetStream::slotFinished
void slotFinished()
Definition: netstream.cpp:418
NAMThread::GetLastModified
static QDateTime GetLastModified(const QUrl &url)
Definition: netstream.cpp:940
NAMThread::~NAMThread
~NAMThread() override
Definition: netstream.cpp:767
NetStreamAbort
Definition: netstream.cpp:77
NetStreamRequest::kType
static const QEvent::Type kType
Definition: netstream.cpp:64
NetStream::WaitTillReady
bool WaitTillReady(std::chrono::milliseconds timeout)
Synchronous interface.
Definition: netstream.cpp:639
NAMThread::isAvailable
static bool isAvailable()
Definition: netstream.cpp:925
quit
@ quit
Definition: lirc_client.h:30
NAMThread::NAMThread
NAMThread()
Definition: netstream.cpp:755
netstream.h
NetStream::kPreferCache
@ kPreferCache
Definition: netstream.h:36
NetStream::IsSupported
static bool IsSupported(const QUrl &url)
RingBuffer interface.
Definition: netstream.cpp:529
mythlogging.h
GetConfDir
QString GetConfDir(void)
Definition: mythdirs.cpp:263
Source
Definition: channelsettings.cpp:93
NetStream::kNeverCache
@ kNeverCache
Definition: netstream.h:36
hardwareprofile.config.p
p
Definition: config.py:33
hardwareprofile.i18n.t
t
Definition: i18n.py:36
NetStream::BytesAvailable
qlonglong BytesAvailable() const
Definition: netstream.cpp:685
NAMThread::run
void run() override
Definition: netstream.cpp:774
NetStream::GetSize
qlonglong GetSize() const
Definition: netstream.cpp:628
NetStreamRequest::NetStreamRequest
NetStreamRequest(int id, const QNetworkRequest &req)
Definition: netstream.cpp:66
GetShareDir
QString GetShareDir(void)
Definition: mythdirs.cpp:261
NetStream::NetStream
NetStream(const QUrl &url, EMode mode=kPreferCache, QByteArray cert=QByteArray())
Network streaming request.
Definition: netstream.cpp:96
NetStream::GetReadPosition
qlonglong GetReadPosition() const
Definition: netstream.cpp:621
NetStream::kClosed
enum NetStream::@17 kClosed
NetStream::Finished
void Finished(QObject *)
NAMThread::requestStarted
void requestStarted(int, QNetworkReply *)
NetStream::safe_read
int safe_read(void *data, unsigned sz, unsigned millisecs=0)
Definition: netstream.cpp:568
NetStream::slotReadyRead
void slotReadyRead()
Definition: netstream.cpp:367
NetStreamAbort::kType
static const QEvent::Type kType
Definition: netstream.cpp:80
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
NetStream::GetError
QNetworkReply::NetworkError GetError() const
Definition: netstream.cpp:673
MythDate::fromString
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
NetStream::isAvailable
static bool isAvailable()
Public helpers.
Definition: netstream.cpp:728
NAMThread::m_mutex
QMutex m_mutex
Definition: netstream.h:154
NAMThread
Thread to process NetStream requests.
Definition: netstream.h:115
NetStream::IsOpen
bool IsOpen() const
Definition: netstream.cpp:537
ContentRange
static qlonglong ContentRange(const QNetworkReply *reply, qulonglong &first, qulonglong &last)
Definition: netstream.cpp:325
NetStream::kStarted
@ kStarted
Definition: netstream.h:100
mythcorecontext.h
NetStream::Seek
qlonglong Seek(qlonglong pos)
Definition: netstream.cpp:600
NAMThread::AbortRequest
static bool AbortRequest(NetStreamAbort *p)
Definition: netstream.cpp:908
NetStream::kPending
@ kPending
Definition: netstream.h:100
NetStreamAbort::m_id
const int m_id
Definition: netstream.cpp:88
NAMThread::m_work
QWaitCondition m_work
Definition: netstream.h:156
NetStream::ReadyRead
void ReadyRead(QObject *)
NetStreamRequest
Definition: netstream.cpp:61
NetStream::kFinished
@ kFinished
Definition: netstream.h:100
NetStreamRequest::m_bCancelled
volatile bool m_bCancelled
Definition: netstream.cpp:74
NetStream::isReady
bool isReady() const
Definition: netstream.cpp:712
NetStream::m_cert
QByteArray m_cert
Definition: netstream.h:106
NAMThread::manager
static NAMThread & manager()
NetworkAccessManager event loop thread.
Definition: netstream.cpp:745
NetStream::m_pos
qlonglong m_pos
Definition: netstream.h:105
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:842
NetStream::~NetStream
~NetStream() override
Definition: netstream.cpp:124
NetStreamAbort::NetStreamAbort
NetStreamAbort(int id, QNetworkReply *reply)
Definition: netstream.cpp:82
d
static const iso6937table * d
Definition: iso6937tables.cpp:1025
NetStream::slotRequestStarted
void slotRequestStarted(int id, QNetworkReply *reply)
Definition: netstream.cpp:279
NetStream::m_mutex
QMutex m_mutex
Definition: netstream.h:97
culrcscrapers.music163.lyricsScraper.headers
dictionary headers
Definition: lyricsScraper.py:19
NAMThread::GetMutex
static QRecursiveMutex * GetMutex()
Definition: netstream.h:129
NetStream::GetErrorString
QString GetErrorString() const
Definition: netstream.cpp:679
NetStream::Abort
void Abort()
Definition: netstream.cpp:543
NetStreamRequest::m_id
const int m_id
Definition: netstream.cpp:72
NetStreamAbort::m_reply
QNetworkReply *const m_reply
Definition: netstream.cpp:89
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:902
NetStream::m_size
qlonglong m_size
Definition: netstream.h:104