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