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