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