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  qulonglong &first, qulonglong &last)
323 {
324  QByteArray range = reply->rawHeader("Content-Range");
325  if (range.isEmpty())
326  return -1;
327 
328  // See RFC 2616 14.16: 'bytes begin-end/size'
329  qulonglong len = 0;
330  if (3 != std::sscanf(range.constData(), " bytes %20lld - %20lld / %20lld", &first, &last, &len))
331  {
332  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Invalid Content-Range:'%1'")
333  .arg(range.constData()) );
334  return -1;
335  }
336 
337  return static_cast<qlonglong>(len);
338 }
339 
340 #if 0
341 static bool inline RequestRange(const QNetworkRequest &request,
342  qlonglong &first, qlonglong &last)
343 {
344  first = last = -1;
345 
346  QByteArray range = request.rawHeader("Range");
347  if (range.isEmpty())
348  return false;
349 
350  if (1 > std::sscanf(range.constData(), " bytes %20lld - %20lld", &first, &last))
351  {
352  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Invalid Range:'%1'")
353  .arg(range.constData()) );
354  return false;
355  }
356 
357  return true;
358 }
359 #endif
360 
361 // signal from QNetworkReply
363 {
364  QMutexLocker locker(&m_mutex);
365 
366  if (m_reply)
367  {
368  qint64 avail = m_reply->bytesAvailable();
369  LOG(VB_FILE, (avail <= 2 * kMaxBuffer) ? LOG_DEBUG :
370  (avail <= 4 * kMaxBuffer) ? LOG_INFO : LOG_WARNING,
371  LOC + QString("(%1) Ready 0x%2, %3 bytes available").arg(m_id)
372  .arg(quintptr(m_reply),0,16).arg(avail) );
373 
374  if (m_size < 0 || m_state < kReady)
375  {
376  qulonglong first = 0;
377  qulonglong last = 0;
378  qlonglong len = ContentRange(m_reply, first, last);
379  if (len >= 0)
380  {
381  m_size = len;
382  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Ready 0x%2, range %3-%4/%5")
383  .arg(m_id).arg(quintptr(m_reply),0,16).arg(first).arg(last).arg(len) );
384  }
385  else
386  {
388  if (m_state < kReady || m_size >= 0)
389  {
390  LOG(VB_FILE, LOG_INFO, LOC +
391  QString("(%1) Ready 0x%2, content length %3")
392  .arg(m_id).arg(quintptr(m_reply),0,16).arg(m_size) );
393  }
394  }
395  }
396 
397  if (m_state < kReady)
398  m_state = kReady;
399 
400  locker.unlock();
401  emit ReadyRead(this);
402  locker.relock();
403 
404  m_ready.wakeAll();
405  }
406  else
407  LOG(VB_GENERAL, LOG_ERR, LOC +
408  QString("(%1) ReadyRead but m_reply = NULL").arg(m_id));
409 }
410 
411 // signal from QNetworkReply
413 {
414  QMutexLocker locker(&m_mutex);
415 
416  if (m_reply)
417  {
418  QNetworkReply::NetworkError error = m_reply->error();
419  if (QNetworkReply::NoError == error)
420  {
421  // Check for a re-direct
422  QUrl url = m_reply->attribute(
423  QNetworkRequest::RedirectionTargetAttribute).toUrl();
424  if (!url.isValid())
425  {
426  m_state = kFinished;
427  }
428  else if (m_nRedirections++ > 0)
429  {
430  LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Too many redirections")
431  .arg(m_id));
432  m_state = kFinished;
433  }
434  else if ((url = m_request.url().resolved(url)) == m_request.url())
435  {
436  LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Redirection loop to %2")
437  .arg(m_id).arg(url.toString()) );
438  m_state = kFinished;
439  }
440  else
441  {
442  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Redirecting").arg(m_id));
443  m_state = Request(url) ? kPending : kFinished;
444  }
445  }
446  else
447  {
448  LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1): %2")
449  .arg(m_id).arg(m_reply->errorString()) );
450  m_state = kFinished;
451  }
452 
453  if (m_state == kFinished)
454  {
455  if (m_size < 0)
456  m_size = m_pos + m_reply->size();
457 
458  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Finished 0x%2 %3/%4 bytes from %5")
459  .arg(m_id).arg(quintptr(m_reply),0,16).arg(m_pos).arg(m_size).arg(Source(m_reply)) );
460 
461  locker.unlock();
462  emit Finished(this);
463  locker.relock();
464 
465  m_finished.wakeAll();
466  }
467  }
468  else
469  LOG(VB_GENERAL, LOG_ERR, LOC + QString("(%1) Finished but m_reply = NULL")
470  .arg(m_id));
471 }
472 
473 #ifndef QT_NO_OPENSSL
474 // signal from QNetworkReply
475 void NetStream::slotSslErrors(const QList<QSslError> &errors)
476 {
477  QMutexLocker locker(&m_mutex);
478 
479  if (m_reply)
480  {
481  bool bIgnore = true;
482  for (const auto& e : qAsConst(errors))
483  {
484  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL error %2: ")
485  .arg(m_id).arg(e.error()) + e.errorString() );
486  switch (e.error())
487  {
488 #if 1 // The BBC use a self certified cert
489  case QSslError::SelfSignedCertificateInChain:
490  break;
491 #endif
492  default:
493  bIgnore = false;
494  break;
495  }
496  }
497 
498  if (bIgnore)
499  {
500  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL errors ignored").arg(m_id));
501  m_reply->ignoreSslErrors(errors);
502  }
503  }
504  else
505  LOG(VB_GENERAL, LOG_ERR, LOC +
506  QString("(%1) SSL error but m_reply = NULL").arg(m_id) );
507 }
508 #endif
509 
510 
514 // static
515 bool NetStream::IsSupported(const QUrl &url)
516 {
517  return url.isValid() &&
518  (url.scheme() == "http" || url.scheme() == "https") &&
519  !url.authority().isEmpty() &&
520  !url.path().isEmpty();
521 }
522 
523 bool NetStream::IsOpen() const
524 {
525  QMutexLocker locker(&m_mutex);
526  return m_state > kClosed;
527 }
528 
530 {
531  QMutexLocker locker(&m_mutex);
532 
533  if (m_pending)
534  {
535  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Cancelled").arg(m_id) );
536  m_pending->m_bCancelled = true;
537  m_pending = nullptr;
538  }
539 
540  if (m_reply)
541  {
542  if (m_state >= kStarted && m_state < kFinished)
543  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Abort 0x%2")
544  .arg(m_id).arg(quintptr(m_reply),0,16) );
545 
547  // NAMthread will delete the reply
548  m_reply = nullptr;
549  }
550 
551  m_state = kFinished;
552 }
553 
554 int NetStream::safe_read(void *data, unsigned sz, unsigned millisecs /* = 0 */)
555 {
556  QElapsedTimer t; t.start();
557  QMutexLocker locker(&m_mutex);
558 
559  if (m_size >= 0 && m_pos >= m_size)
560  return 0; // EOF
561 
562  while (m_state < kFinished && (!m_reply || m_reply->bytesAvailable() < sz))
563  {
564  unsigned elapsed = t.elapsed();
565  if (elapsed >= millisecs)
566  break;
567  m_ready.wait(&m_mutex, millisecs - elapsed);
568  }
569 
570  locker.unlock();
571  QMutexLocker lockNAM(NAMThread::GetMutex());
572  locker.relock();
573  if (!m_reply)
574  return -1;
575 
576  qint64 avail = m_reply->read(reinterpret_cast< char* >(data), sz);
577  if (avail <= 0)
578  return m_state >= kFinished ? 0 : -1; // 0= EOF
579 
580  LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) safe_read @ %4 => %2/%3, %5 mS")
581  .arg(m_id).arg(avail).arg(sz).arg(m_pos).arg(t.elapsed()) );
582  m_pos += avail;
583  return (int)avail;
584 }
585 
586 qlonglong NetStream::Seek(qlonglong pos)
587 {
588  QMutexLocker locker(&m_mutex);
589 
590  if (pos == m_pos)
591  return pos;
592 
593  if (pos < 0 || (m_size >= 0 && pos > m_size))
594  {
595  LOG(VB_GENERAL, LOG_ERR, LOC +
596  QString("(%1) Seek(%2) out of range [0..%3]")
597  .arg(m_id).arg(pos).arg(m_size) );
598  return -1;
599  }
600 
601  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Seek(%2) curr %3 end %4")
602  .arg(m_id).arg(pos).arg(m_pos).arg(m_size) );
603  m_pos = pos;
604  return Request(m_request.url()) ? m_pos : -1;
605 }
606 
607 qlonglong NetStream::GetReadPosition() const
608 {
609  QMutexLocker locker(&m_mutex);
610 
611  return m_pos;
612 }
613 
614 qlonglong NetStream::GetSize() const
615 {
616  QMutexLocker locker(&m_mutex);
617 
618  return m_size;
619 }
620 
621 
625 bool NetStream::WaitTillReady(unsigned long milliseconds)
626 {
627  QMutexLocker locker(&m_mutex);
628 
629  QElapsedTimer t; t.start();
630  while (m_state < kReady)
631  {
632  unsigned elapsed = t.elapsed();
633  if (elapsed > milliseconds)
634  return false;
635 
636  m_ready.wait(&m_mutex, milliseconds - elapsed);
637  }
638 
639  return true;
640 }
641 
642 bool NetStream::WaitTillFinished(unsigned long milliseconds)
643 {
644  QMutexLocker locker(&m_mutex);
645 
646  QElapsedTimer t; t.start();
647  while (m_state < kFinished)
648  {
649  unsigned elapsed = t.elapsed();
650  if (elapsed > milliseconds)
651  return false;
652 
653  m_finished.wait(&m_mutex, milliseconds - elapsed);
654  }
655 
656  return true;
657 }
658 
659 QNetworkReply::NetworkError NetStream::GetError() const
660 {
661  QMutexLocker locker(&m_mutex);
662  return !m_reply ? QNetworkReply::OperationCanceledError : m_reply->error();
663 }
664 
666 {
667  QMutexLocker locker(&m_mutex);
668  return !m_reply ? "Operation cancelled" : m_reply->errorString();
669 }
670 
671 qlonglong NetStream::BytesAvailable() const
672 {
673  QMutexLocker locker(&m_mutex);
674  return m_reply ? m_reply->bytesAvailable() : 0;
675 }
676 
677 QByteArray NetStream::ReadAll()
678 {
679  QMutexLocker locker(&m_mutex);
680 
681  if (!m_reply)
682  return nullptr;
683 
684  QByteArray data = m_reply->readAll();
685  m_pos += data.size();
686  return data;
687 }
688 
693 {
694  QMutexLocker locker(&m_mutex);
695  return m_state >= kStarted;
696 }
697 
698 bool NetStream::isReady() const
699 {
700  QMutexLocker locker(&m_mutex);
701  return m_state >= kReady;
702 }
703 
705 {
706  QMutexLocker locker(&m_mutex);
707  return m_state >= kFinished;
708 }
709 
713 // static
715 {
716  return NAMThread::isAvailable();
717 }
718 
719 // Time when URI was last written to cache or invalid if not cached.
720 // static
721 QDateTime NetStream::GetLastModified(const QUrl &url)
722 {
723  return NAMThread::GetLastModified(url);
724 }
725 
726 
730 //static
732 {
733  QMutexLocker locker(&s_mtx);
734 
735  // Singleton
736  static NAMThread s_thread;
737  s_thread.start();
738  return s_thread;
739 }
740 
742 {
743  setObjectName("NAMThread");
744 
745 #ifndef QT_NO_OPENSSL
746  // This ought to be done by the Qt lib but isn't in 4.7
747  //Q_DECLARE_METATYPE(QList<QSslError>)
748  qRegisterMetaType< QList<QSslError> >();
749 #endif
750 }
751 
752 // virtual
754 {
755  QMutexLocker locker(&m_mutex);
756  delete m_nam;
757 }
758 
759 // virtual
761 {
762  LOG(VB_FILE, LOG_INFO, LOC "NAMThread starting");
763 
764  m_nam = new QNetworkAccessManager();
765  m_nam->setObjectName("NetStream NAM");
766 
767  // Setup cache
768  QScopedPointer<QNetworkDiskCache> cache(new QNetworkDiskCache());
769 
770  cache->setCacheDirectory(GetConfDir() + "/cache/netstream-" +
772 
773  m_nam->setCache(cache.take());
774 
775  // Setup a network proxy e.g. for TOR: socks://localhost:9050
776  // TODO get this from mythdb
777  QString proxy(getenv("MYTHMHEG_PROXY"));
778  if (!proxy.isEmpty())
779  {
780  QUrl url(proxy, QUrl::TolerantMode);
781  QNetworkProxy::ProxyType type =
782  url.scheme().isEmpty() ? QNetworkProxy::HttpProxy :
783  url.scheme() == "socks" ? QNetworkProxy::Socks5Proxy :
784  url.scheme() == "http" ? QNetworkProxy::HttpProxy :
785  url.scheme() == "https" ? QNetworkProxy::HttpProxy :
786  url.scheme() == "cache" ? QNetworkProxy::HttpCachingProxy :
787  url.scheme() == "ftp" ? QNetworkProxy::FtpCachingProxy :
788  QNetworkProxy::NoProxy;
789  if (QNetworkProxy::NoProxy != type)
790  {
791  LOG(VB_GENERAL, LOG_INFO, LOC "Using proxy: " + proxy);
792  m_nam->setProxy(QNetworkProxy(
793  type, url.host(), url.port(), url.userName(), url.password() ));
794  }
795  else
796  {
797  LOG(VB_MHEG, LOG_ERR, LOC + QString("Unknown proxy type %1")
798  .arg(url.scheme()) );
799  }
800  }
801 
802  // Quit when main app quits
803  connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(quit()) );
804 
805  m_running.release();
806 
807  QMutexLocker lockNAM(&m_mutexNAM);
808  while(!m_bQuit)
809  {
810  // Process NAM events
811  QCoreApplication::processEvents();
812 
813  lockNAM.unlock();
814 
815  QMutexLocker locker(&m_mutex);
816  m_work.wait(&m_mutex, 100);
817 
818  lockNAM.relock();
819 
820  while (!m_workQ.isEmpty())
821  {
822  QScopedPointer< QEvent > ev(m_workQ.dequeue());
823  locker.unlock();
824  NewRequest(ev.data());
825  locker.relock();
826  }
827  }
828 
829  m_running.acquire();
830 
831  delete m_nam;
832  m_nam = nullptr;
833 
834  LOG(VB_FILE, LOG_INFO, LOC "NAMThread stopped");
835 }
836 
837 // slot
839 {
840  m_bQuit = true;
841  QThread::quit();
842 }
843 
844 void NAMThread::Post(QEvent *event)
845 {
846  QMutexLocker locker(&m_mutex);
847  m_workQ.enqueue(event);
848 }
849 
850 bool NAMThread::NewRequest(QEvent *event)
851 {
852  switch (event->type())
853  {
855  return StartRequest(dynamic_cast< NetStreamRequest* >(event));
856 #pragma GCC diagnostic push
857 #pragma GCC diagnostic ignored "-Wswitch"
859  return AbortRequest(dynamic_cast< NetStreamAbort* >(event));
860 #pragma GCC diagnostic pop
861  default:
862  break;
863  }
864  return false;
865 }
866 
868 {
869  if (!p)
870  {
871  LOG(VB_GENERAL, LOG_ERR, LOC "Invalid NetStreamRequest");
872  return false;
873  }
874 
875  if (!p->m_bCancelled)
876  {
877  QNetworkReply *reply = m_nam->get(p->m_req);
878  LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) StartRequest 0x%2")
879  .arg(p->m_id).arg(quintptr(reply),0,16) );
880  emit requestStarted(p->m_id, reply);
881  }
882  else
883  LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) NetStreamRequest cancelled").arg(p->m_id) );
884  return true;
885 }
886 
888 {
889  if (!p)
890  {
891  LOG(VB_GENERAL, LOG_ERR, LOC "Invalid NetStreamAbort");
892  return false;
893  }
894 
895  LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) AbortRequest 0x%2").arg(p->m_id)
896  .arg(quintptr(p->m_reply),0,16) );
897  p->m_reply->abort();
898  p->m_reply->disconnect();
899  delete p->m_reply;
900  return true;
901 }
902 
903 // static
905 {
906  NAMThread &m = manager();
907 
908  if (!m.m_running.tryAcquire(1, 3000))
909  return false;
910 
911  m.m_running.release();
912 
913  QMutexLocker locker(&m.m_mutex);
914 
915  if (!m.m_nam)
916  return false;
917 
918  switch (m.m_nam->networkAccessible())
919  {
920  case QNetworkAccessManager::Accessible: return true;
921  case QNetworkAccessManager::NotAccessible: return false;
922  case QNetworkAccessManager::UnknownAccessibility: return true;
923  }
924  return false;
925 }
926 
927 // Time when URI was last written to cache or invalid if not cached.
928 // static
929 QDateTime NAMThread::GetLastModified(const QUrl &url)
930 {
931  NAMThread &m = manager();
932 
933  QMutexLocker locker(&m.m_mutex);
934 
935  if (!m.m_nam)
936  return QDateTime(); // Invalid
937 
938  QAbstractNetworkCache *cache = m.m_nam->cache();
939  if (!cache)
940  return QDateTime(); // Invalid
941 
942  QNetworkCacheMetaData meta = cache->metaData(url);
943  if (!meta.isValid())
944  {
945  LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') not in cache")
946  .arg(url.toString()));
947  return QDateTime(); // Invalid
948  }
949 
950  // Check if expired
951  QDateTime const now(QDateTime::currentDateTime()); // local time
952  QDateTime expire = meta.expirationDate();
953  if (expire.isValid() && expire.toLocalTime() < now)
954  {
955  LOG(VB_FILE, LOG_INFO, LOC + QString("GetLastModified('%1') past expiration %2")
956  .arg(url.toString()).arg(expire.toString()));
957  return QDateTime(); // Invalid
958  }
959 
960  // Get time URI was modified (Last-Modified header) NB this may be invalid
961  QDateTime lastMod = meta.lastModified();
962 
963  QNetworkCacheMetaData::RawHeaderList headers = meta.rawHeaders();
964  for (const auto& h : qAsConst(headers))
965  {
966  // RFC 1123 date format: Thu, 01 Dec 1994 16:00:00 GMT
967  static const QString kSzFormat { "ddd, dd MMM yyyy HH:mm:ss 'GMT'" };
968 
969  QString const first(h.first.toLower());
970  if (first == "cache-control")
971  {
972  QString const second(h.second.toLower());
973  if (second == "no-cache" || second == "no-store")
974  {
975  LOG(VB_FILE, LOG_INFO, LOC +
976  QString("GetLastModified('%1') Cache-Control disabled")
977  .arg(url.toString()) );
978  cache->remove(url);
979  return QDateTime(); // Invalid
980  }
981  }
982  else if (first == "date")
983  {
984  QDateTime d = QDateTime::fromString(h.second, kSzFormat);
985  if (!d.isValid())
986  {
987  LOG(VB_GENERAL, LOG_WARNING, LOC +
988  QString("GetLastModified invalid Date header '%1'")
989  .arg(h.second.constData()));
990  continue;
991  }
992  d.setTimeSpec(Qt::UTC);
993  lastMod = d;
994  }
995  }
996 
997  LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') last modified %2")
998  .arg(url.toString()).arg(lastMod.toString()));
999  return lastMod;
1000 }
1001 
1002 /* End of file */
NetStream::ReadAll
QByteArray ReadAll()
Definition: netstream.cpp:677
Source
static QString Source(const QNetworkRequest &request)
Definition: netstream.cpp:137
NetStream::slotSslErrors
void slotSslErrors(const QList< QSslError > &errors)
Definition: netstream.cpp:475
e
QDomElement e
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:1417
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:74
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:867
false
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:85
NetStream::Request
bool Request(const QUrl &url)
Definition: netstream.cpp:157
NetStream::kPending
@ kPending
Definition: netstream.h:98
s_mtx
static QMutex s_mtx
Definition: netstream.cpp:53
NetStream::isStarted
bool isStarted() const
Asynchronous interface.
Definition: netstream.cpp:692
NetStream::GetLastModified
static QDateTime GetLastModified(const QUrl &url)
Definition: netstream.cpp:721
NetStream::isFinished
bool isFinished() const
Definition: netstream.cpp:704
NAMThread::m_running
QSemaphore m_running
Definition: netstream.h:149
NAMThread::NewRequest
bool NewRequest(QEvent *event)
Definition: netstream.cpp:850
d
static const uint16_t * d
Definition: iso6937tables.cpp:1025
ContentLength
static qlonglong ContentLength(const QNetworkReply *reply)
Definition: netstream.cpp:313
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:838
NAMThread::Post
void Post(QEvent *event)
Definition: netstream.cpp:844
NetStream::WaitTillFinished
bool WaitTillFinished(unsigned long milliseconds)
Definition: netstream.cpp:642
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:412
NAMThread::GetLastModified
static QDateTime GetLastModified(const QUrl &url)
Definition: netstream.cpp:929
NAMThread::~NAMThread
~NAMThread() override
Definition: netstream.cpp:753
kMaxBuffer
const qint64 kMaxBuffer
Definition: netstream.cpp:54
NetStreamAbort
Definition: netstream.cpp:79
NetStream::kClosed
enum NetStream::@19 kClosed
NetStreamRequest::kType
static const QEvent::Type kType
Definition: netstream.cpp:64
NAMThread::isAvailable
static bool isAvailable()
Definition: netstream.cpp:904
quit
@ quit
Definition: lirc_client.h:30
NAMThread::NAMThread
NAMThread()
Definition: netstream.cpp:741
netstream.h
NetStream::IsSupported
static bool IsSupported(const QUrl &url)
RingBuffer interface.
Definition: netstream.cpp:515
mythlogging.h
GetConfDir
QString GetConfDir(void)
Definition: mythdirs.cpp:224
Source
Definition: channelsettings.cpp:69
NetStream::kFinished
@ kFinished
Definition: netstream.h:98
hardwareprofile.config.p
p
Definition: config.py:33
NetStream::WaitTillReady
bool WaitTillReady(unsigned long milliseconds)
Synchronous interface.
Definition: netstream.cpp:625
hardwareprofile.i18n.t
t
Definition: i18n.py:36
NetStream::BytesAvailable
qlonglong BytesAvailable() const
Definition: netstream.cpp:671
NAMThread::run
void run() override
Definition: netstream.cpp:760
NetStream::GetSize
qlonglong GetSize() const
Definition: netstream.cpp:614
NetStreamRequest::NetStreamRequest
NetStreamRequest(int id, const QNetworkRequest &req)
Definition: netstream.cpp:66
GetShareDir
QString GetShareDir(void)
Definition: mythdirs.cpp:222
NetStream::NetStream
NetStream(const QUrl &url, EMode mode=kPreferCache, QByteArray cert=QByteArray())
Network streaming request.
Definition: netstream.cpp:97
NetStream::GetReadPosition
qlonglong GetReadPosition() const
Definition: netstream.cpp:607
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:554
NetStream::kStarted
@ kStarted
Definition: netstream.h:98
NetStream::slotReadyRead
void slotReadyRead()
Definition: netstream.cpp:362
NetStreamAbort::kType
static const QEvent::Type kType
Definition: netstream.cpp:81
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:57
NetStream::GetError
QNetworkReply::NetworkError GetError() const
Definition: netstream.cpp:659
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:714
NAMThread::m_mutex
QMutex m_mutex
Definition: netstream.h:152
NetStream::kReady
@ kReady
Definition: netstream.h:98
NAMThread
Thread to process NetStream requests.
Definition: netstream.h:114
NAMThread::GetMutex
static QMutex * GetMutex()
Definition: netstream.h:127
NetStream::IsOpen
bool IsOpen() const
Definition: netstream.cpp:523
ContentRange
static qlonglong ContentRange(const QNetworkReply *reply, qulonglong &first, qulonglong &last)
Definition: netstream.cpp:321
mythcorecontext.h
NetStream::Seek
qlonglong Seek(qlonglong pos)
Definition: netstream.cpp:586
NAMThread::AbortRequest
static bool AbortRequest(NetStreamAbort *p)
Definition: netstream.cpp:887
NetStreamAbort::m_id
const int m_id
Definition: netstream.cpp:89
NAMThread::m_work
QWaitCondition m_work
Definition: netstream.h:154
NetStream::ReadyRead
void ReadyRead(QObject *)
NetStreamRequest
Definition: netstream.cpp:62
NetStream::kPreferCache
@ kPreferCache
Definition: netstream.h:35
NetStreamRequest::m_bCancelled
volatile bool m_bCancelled
Definition: netstream.cpp:75
NetStream::isReady
bool isReady() const
Definition: netstream.cpp:698
NetStream::m_cert
QByteArray m_cert
Definition: netstream.h:104
NAMThread::manager
static NAMThread & manager()
NetworkAccessManager event loop thread.
Definition: netstream.cpp:731
NetStream::m_pos
qlonglong m_pos
Definition: netstream.h:103
LOC
#define LOC
Definition: netstream.cpp:46
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:856
NetStream::~NetStream
~NetStream() override
Definition: netstream.cpp:122
NetStreamAbort::NetStreamAbort
NetStreamAbort(int id, QNetworkReply *reply)
Definition: netstream.cpp:83
NetStream::slotRequestStarted
void slotRequestStarted(int id, QNetworkReply *reply)
Definition: netstream.cpp:277
NetStream::kNeverCache
@ kNeverCache
Definition: netstream.h:35
NetStream::m_mutex
QMutex m_mutex
Definition: netstream.h:96
NetStream::GetErrorString
QString GetErrorString() const
Definition: netstream.cpp:665
NetStream::Abort
void Abort()
Definition: netstream.cpp:529
NetStreamRequest::m_id
const int m_id
Definition: netstream.cpp:73
NetStreamAbort::m_reply
QNetworkReply *const m_reply
Definition: netstream.cpp:90
NetStream::kAlwaysCache
@ kAlwaysCache
Definition: netstream.h:35
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:916
NetStream::m_size
qlonglong m_size
Definition: netstream.h:102