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