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