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