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