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