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