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