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 QSslCertificate cert;
226 QFile f1(QFile::exists(fname) ? fname : GetShareDir() + fname);
227 if (f1.open(QIODevice::ReadOnly))
228 {
229 cert = QSslCertificate(&f1, QSsl::Pem);
230 if (!cert.isNull())
231 ssl.setLocalCertificate(cert);
232 else
233 LOG(VB_GENERAL, LOG_WARNING, LOC +
234 QString("'%1' is an invalid certificate").arg(f1.fileName()) );
235 }
236 else
237 {
238 LOG(VB_GENERAL, LOG_WARNING, LOC +
239 QString("Opening client certificate '%1': %2")
240 .arg(f1.fileName(), f1.errorString()) );
241 }
242
243 // Get the private key
244 fname = gCoreContext->GetSetting("MhegClientKey", "");
245 if (!fname.isEmpty())
246 {
247 QFile f2(QFile::exists(fname) ? fname : GetShareDir() + fname);
248 if (f2.open(QIODevice::ReadOnly))
249 {
250 auto keyAlgo = cert.isNull() ? QSsl::Rsa : cert.publicKey().algorithm();
251 QSslKey key(&f2, keyAlgo, QSsl::Pem, QSsl::PrivateKey,
252 gCoreContext->GetSetting("MhegClientKeyPass", "").toLatin1());
253 if (!key.isNull())
254 ssl.setPrivateKey(key);
255 else
256 LOG(VB_GENERAL, LOG_WARNING, LOC +
257 QString("'%1' is an invalid key").arg(f2.fileName()) );
258 }
259 else
260 {
261 LOG(VB_GENERAL, LOG_WARNING, LOC +
262 QString("Opening private key '%1': %2")
263 .arg(f2.fileName(), f2.errorString()) );
264 }
265 }
266 }
267
268 m_request.setSslConfiguration(ssl);
269 }
270#endif
271
272 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Request %2 bytes=%3- from %4")
273 .arg(m_id).arg(m_request.url().toString())
274 .arg(m_pos).arg(Source(m_request)) );
277 return true;
278}
279
280// signal from NAMThread manager that a request has been started
281void NetStream::slotRequestStarted(int id, QNetworkReply *reply)
282{
283 QMutexLocker locker(&m_mutex);
284
285 if (m_id != id)
286 return;
287
288 m_pending = nullptr; // Event is no longer valid
289
290 if (!m_reply)
291 {
292 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Started 0x%2")
293 .arg(m_id).arg(quintptr(reply),0,16) );
294
295 m_reply = reply;
296 m_state = kStarted;
297
298 reply->setReadBufferSize(kMaxBuffer);
299
300 // NB The following signals must be Qt::DirectConnection 'cos this slot
301 // was connected Qt::DirectConnection so the current thread is NAMThread
302
303 // QNetworkReply signals
304 connect(reply, &QNetworkReply::finished, this, &NetStream::slotFinished, Qt::DirectConnection );
305#ifndef QT_NO_OPENSSL
306 connect(reply, &QNetworkReply::sslErrors, this,
307 &NetStream::slotSslErrors, Qt::DirectConnection );
308#endif
309 // QIODevice signals
310 connect(reply, &QIODevice::readyRead, this, &NetStream::slotReadyRead, Qt::DirectConnection );
311 }
312 else
313 {
314 LOG(VB_GENERAL, LOG_ERR, LOC +
315 QString("(%1) Started but m_reply not NULL").arg(m_id));
316 }
317}
318
319static qlonglong inline ContentLength(const QNetworkReply *reply)
320{
321 bool ok = false;
322 qlonglong len = reply->header(QNetworkRequest::ContentLengthHeader)
323 .toLongLong(&ok);
324 return ok ? len : -1;
325}
326
327static qlonglong inline ContentRange(const QNetworkReply *reply,
328 qulonglong &first, qulonglong &last)
329{
330 QByteArray range = reply->rawHeader("Content-Range");
331 if (range.isEmpty())
332 return -1;
333
334 // See RFC 2616 14.16: 'bytes begin-end/size'
335 qulonglong len = 0;
336 const char *fmt = " bytes %20" SCNd64 " - %20" SCNd64 " / %20" SCNd64;
337 if (3 != std::sscanf(range.constData(), fmt, &first, &last, &len))
338 {
339 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Invalid Content-Range:'%1'")
340 .arg(range.constData()) );
341 return -1;
342 }
343
344 return static_cast<qlonglong>(len);
345}
346
347#if 0
348static bool inline RequestRange(const QNetworkRequest &request,
349 qlonglong &first, qlonglong &last)
350{
351 first = last = -1;
352
353 QByteArray range = request.rawHeader("Range");
354 if (range.isEmpty())
355 return false;
356
357 if (1 > std::sscanf(range.constData(), " bytes %20lld - %20lld", &first, &last))
358 {
359 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Invalid Range:'%1'")
360 .arg(range.constData()) );
361 return false;
362 }
363
364 return true;
365}
366#endif
367
368// signal from QNetworkReply
370{
371 QMutexLocker locker(&m_mutex);
372
373 if (m_reply)
374 {
375 qint64 avail = m_reply->bytesAvailable();
376 LOG(VB_FILE, (avail <= 2 * kMaxBuffer) ? LOG_DEBUG :
377 (avail <= 4 * kMaxBuffer) ? LOG_INFO : LOG_WARNING,
378 LOC + QString("(%1) Ready 0x%2, %3 bytes available").arg(m_id)
379 .arg(quintptr(m_reply),0,16).arg(avail) );
380
381 if (m_size < 0 || m_state < kReady)
382 {
383 qulonglong first = 0;
384 qulonglong last = 0;
385 qlonglong len = ContentRange(m_reply, first, last);
386 if (len >= 0)
387 {
388 m_size = len;
389 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Ready 0x%2, range %3-%4/%5")
390 .arg(m_id).arg(quintptr(m_reply),0,16).arg(first).arg(last).arg(len) );
391 }
392 else
393 {
395 if (m_state < kReady || m_size >= 0)
396 {
397 LOG(VB_FILE, LOG_INFO, LOC +
398 QString("(%1) Ready 0x%2, content length %3")
399 .arg(m_id).arg(quintptr(m_reply),0,16).arg(m_size) );
400 }
401 }
402 }
403
404 m_state = std::max(m_state, kReady);
405
406 locker.unlock();
407 emit ReadyRead(this);
408 locker.relock();
409
410 m_ready.wakeAll();
411 }
412 else
413 {
414 LOG(VB_GENERAL, LOG_ERR, LOC +
415 QString("(%1) ReadyRead but m_reply = NULL").arg(m_id));
416 }
417}
418
419// signal from QNetworkReply
421{
422 QMutexLocker locker(&m_mutex);
423
424 if (m_reply)
425 {
426 QNetworkReply::NetworkError error = m_reply->error();
427 if (QNetworkReply::NoError == error)
428 {
429 // Check for a re-direct
430 QUrl url = m_reply->attribute(
431 QNetworkRequest::RedirectionTargetAttribute).toUrl();
432 if (!url.isValid())
433 {
434 m_state = kFinished;
435 }
436 else if (m_nRedirections++ > 0)
437 {
438 LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Too many redirections")
439 .arg(m_id));
440 m_state = kFinished;
441 }
442 else
443 {
444 url = m_request.url().resolved(url);
445 if (url == m_request.url())
446 {
447 LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1) Redirection loop to %2")
448 .arg(m_id).arg(url.toString()) );
449 m_state = kFinished;
450 }
451 else
452 {
453 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Redirecting").arg(m_id));
454 m_state = Request(url) ? kPending : kFinished;
455 }
456 }
457 }
458 else
459 {
460 LOG(VB_FILE, LOG_WARNING, LOC + QString("(%1): %2")
461 .arg(m_id).arg(m_reply->errorString()) );
462 m_state = kFinished;
463 }
464
465 if (m_state == kFinished)
466 {
467 if (m_size < 0)
468 m_size = m_pos + m_reply->size();
469
470 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Finished 0x%2 %3/%4 bytes from %5")
471 .arg(m_id).arg(quintptr(m_reply),0,16).arg(m_pos).arg(m_size).arg(Source(m_reply)) );
472
473 locker.unlock();
474 emit Finished(this);
475 locker.relock();
476
477 m_finished.wakeAll();
478 }
479 }
480 else
481 {
482 LOG(VB_GENERAL, LOG_ERR, LOC + QString("(%1) Finished but m_reply = NULL")
483 .arg(m_id));
484 }
485}
486
487#ifndef QT_NO_OPENSSL
488// signal from QNetworkReply
489void NetStream::slotSslErrors(const QList<QSslError> &errors)
490{
491 QMutexLocker locker(&m_mutex);
492
493 if (m_reply)
494 {
495 bool bIgnore = true;
496 for (const auto& e : std::as_const(errors))
497 {
498 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL error %2: ")
499 .arg(m_id).arg(e.error()) + e.errorString() );
500 switch (e.error())
501 {
502#if 1 // The BBC use a self certified cert
503 case QSslError::SelfSignedCertificateInChain:
504 break;
505#endif
506 default:
507 bIgnore = false;
508 break;
509 }
510 }
511
512 if (bIgnore)
513 {
514 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) SSL errors ignored").arg(m_id));
515 m_reply->ignoreSslErrors(errors);
516 }
517 }
518 else
519 {
520 LOG(VB_GENERAL, LOG_ERR, LOC +
521 QString("(%1) SSL error but m_reply = NULL").arg(m_id) );
522 }
523}
524#endif
525
526
530// static
531bool NetStream::IsSupported(const QUrl &url)
532{
533 return url.isValid() &&
534 (url.scheme() == "http" || url.scheme() == "https") &&
535 !url.authority().isEmpty() &&
536 !url.path().isEmpty();
537}
538
540{
541 QMutexLocker locker(&m_mutex);
542 return m_state > kClosed;
543}
544
546{
547 QMutexLocker locker(&m_mutex);
548
549 if (m_pending)
550 {
551 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Cancelled").arg(m_id) );
552 m_pending->m_bCancelled = true;
553 m_pending = nullptr;
554 }
555
556 if (m_reply)
557 {
558 if (m_state >= kStarted && m_state < kFinished)
559 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Abort 0x%2")
560 .arg(m_id).arg(quintptr(m_reply),0,16) );
561
563 // NAMthread will delete the reply
564 m_reply = nullptr;
565 }
566
567 m_state = kFinished;
568}
569
570int NetStream::safe_read(void *data, unsigned sz, unsigned millisecs /* = 0 */)
571{
572 QElapsedTimer t; t.start();
573 QMutexLocker locker(&m_mutex);
574
575 if (m_size >= 0 && m_pos >= m_size)
576 return 0; // EOF
577
578 while (m_state < kFinished && (!m_reply || m_reply->bytesAvailable() < sz))
579 {
580 unsigned elapsed = t.elapsed();
581 if (elapsed >= millisecs)
582 break;
583 m_ready.wait(&m_mutex, millisecs - elapsed);
584 }
585
586 locker.unlock();
587 QMutexLocker lockNAM(NAMThread::GetMutex());
588 locker.relock();
589 if (!m_reply)
590 return -1;
591
592 qint64 avail = m_reply->read(reinterpret_cast< char* >(data), sz);
593 if (avail <= 0)
594 return m_state >= kFinished ? 0 : -1; // 0= EOF
595
596 LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) safe_read @ %4 => %2/%3, %5 mS")
597 .arg(m_id).arg(avail).arg(sz).arg(m_pos).arg(t.elapsed()) );
598 m_pos += avail;
599 return (int)avail;
600}
601
602qlonglong NetStream::Seek(qlonglong pos)
603{
604 QMutexLocker locker(&m_mutex);
605
606 if (pos == m_pos)
607 return pos;
608
609 if (pos < 0 || (m_size >= 0 && pos > m_size))
610 {
611 LOG(VB_GENERAL, LOG_ERR, LOC +
612 QString("(%1) Seek(%2) out of range [0..%3]")
613 .arg(m_id).arg(pos).arg(m_size) );
614 return -1;
615 }
616
617 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) Seek(%2) curr %3 end %4")
618 .arg(m_id).arg(pos).arg(m_pos).arg(m_size) );
619 m_pos = pos;
620 return Request(m_request.url()) ? m_pos : -1;
621}
622
624{
625 QMutexLocker locker(&m_mutex);
626
627 return m_pos;
628}
629
630qlonglong NetStream::GetSize() const
631{
632 QMutexLocker locker(&m_mutex);
633
634 return m_size;
635}
636
637
641bool NetStream::WaitTillReady(std::chrono::milliseconds timeout)
642{
643 QMutexLocker locker(&m_mutex);
644
645 QElapsedTimer t; t.start();
646 while (m_state < kReady)
647 {
648 auto elapsed = std::chrono::milliseconds(t.elapsed());
649 if (elapsed > timeout)
650 return false;
651
652 m_ready.wait(&m_mutex, (timeout - elapsed).count());
653 }
654
655 return true;
656}
657
658bool NetStream::WaitTillFinished(std::chrono::milliseconds timeout)
659{
660 QMutexLocker locker(&m_mutex);
661
662 QElapsedTimer t; t.start();
663 while (m_state < kFinished)
664 {
665 auto elapsed = std::chrono::milliseconds(t.elapsed());
666 if (elapsed > timeout)
667 return false;
668
669 m_finished.wait(&m_mutex, (timeout - elapsed).count());
670 }
671
672 return true;
673}
674
675QNetworkReply::NetworkError NetStream::GetError() const
676{
677 QMutexLocker locker(&m_mutex);
678 return !m_reply ? QNetworkReply::OperationCanceledError : m_reply->error();
679}
680
682{
683 QMutexLocker locker(&m_mutex);
684 return !m_reply ? "Operation cancelled" : m_reply->errorString();
685}
686
688{
689 QMutexLocker locker(&m_mutex);
690 return m_reply ? m_reply->bytesAvailable() : 0;
691}
692
694{
695 QMutexLocker locker(&m_mutex);
696
697 if (!m_reply)
698 return nullptr;
699
700 QByteArray data = m_reply->readAll();
701 m_pos += data.size();
702 return data;
703}
704
709{
710 QMutexLocker locker(&m_mutex);
711 return m_state >= kStarted;
712}
713
715{
716 QMutexLocker locker(&m_mutex);
717 return m_state >= kReady;
718}
719
721{
722 QMutexLocker locker(&m_mutex);
723 return m_state >= kFinished;
724}
725
729// static
731{
732 return NAMThread::isAvailable();
733}
734
735// Time when URI was last written to cache or invalid if not cached.
736// static
737QDateTime NetStream::GetLastModified(const QUrl &url)
738{
739 return NAMThread::GetLastModified(url);
740}
741
742
746//static
748{
749 QMutexLocker locker(&s_mtx);
750
751 // Singleton
752 static NAMThread s_thread;
753 s_thread.start();
754 return s_thread;
755}
756
758{
759 setObjectName("NAMThread");
760
761#ifndef QT_NO_OPENSSL
762 // This ought to be done by the Qt lib but isn't in 4.7
763 //Q_DECLARE_METATYPE(QList<QSslError>)
764 qRegisterMetaType< QList<QSslError> >();
765#endif
766}
767
768// virtual
770{
771 QMutexLocker locker(&m_mutex);
772 delete m_nam;
773}
774
775// virtual
777{
778 LOG(VB_FILE, LOG_INFO, LOC + "NAMThread starting");
779
780 m_nam = new QNetworkAccessManager();
781 m_nam->setObjectName("NetStream NAM");
782
783 // Setup cache
784 std::unique_ptr<QNetworkDiskCache> cache = std::make_unique<QNetworkDiskCache>();
785
786 cache->setCacheDirectory(GetConfDir() + "/cache/netstream-" +
788
789 m_nam->setCache(cache.release());
790
791 // Setup a network proxy e.g. for TOR: socks://localhost:9050
792 // TODO get this from mythdb
793 QString proxy(qEnvironmentVariable("MYTHMHEG_PROXY"));
794 if (!proxy.isEmpty())
795 {
796 QUrl url(proxy, QUrl::TolerantMode);
797 QNetworkProxy::ProxyType type {QNetworkProxy::NoProxy};
798 if (url.scheme().isEmpty()
799 || (url.scheme() == "http")
800 || (url.scheme() == "https"))
801 type = QNetworkProxy::HttpProxy;
802 else if (url.scheme() == "socks")
803 type = QNetworkProxy::Socks5Proxy;
804 else if (url.scheme() == "cache")
805 type = QNetworkProxy::HttpCachingProxy;
806 else if (url.scheme() == "ftp")
807 type = QNetworkProxy::FtpCachingProxy;
808
809 if (QNetworkProxy::NoProxy != type)
810 {
811 LOG(VB_GENERAL, LOG_INFO, LOC + "Using proxy: " + proxy);
812 m_nam->setProxy(QNetworkProxy(
813 type, url.host(), url.port(), url.userName(), url.password() ));
814 }
815 else
816 {
817 LOG(VB_MHEG, LOG_ERR, LOC + QString("Unknown proxy type %1")
818 .arg(url.scheme()) );
819 }
820 }
821
822 // Quit when main app quits
823 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
824 this, &NAMThread::quit);
825
826 m_running.release();
827
828 QMutexLocker lockNAM(&m_mutexNAM);
829 while(!m_bQuit)
830 {
831 // Process NAM events
832 QCoreApplication::processEvents();
833
834 lockNAM.unlock();
835
836 QMutexLocker locker(&m_mutex);
837 m_work.wait(&m_mutex, 100);
838
839 lockNAM.relock();
840
841 while (!m_workQ.isEmpty())
842 {
843 QScopedPointer< QEvent > ev(m_workQ.dequeue());
844 locker.unlock();
845 NewRequest(ev.data());
846 locker.relock();
847 }
848 }
849
850 m_running.acquire();
851
852 delete m_nam;
853 m_nam = nullptr;
854
855 LOG(VB_FILE, LOG_INFO, LOC + "NAMThread stopped");
856}
857
858// slot
860{
861 m_bQuit = true;
863}
864
865void NAMThread::Post(QEvent *event)
866{
867 QMutexLocker locker(&m_mutex);
868 m_workQ.enqueue(event);
869}
870
871bool NAMThread::NewRequest(QEvent *event)
872{
873 switch (event->type())
874 {
876 return StartRequest(dynamic_cast< NetStreamRequest* >(event));
877#pragma GCC diagnostic push
878#pragma GCC diagnostic ignored "-Wswitch"
880 return AbortRequest(dynamic_cast< NetStreamAbort* >(event));
881#pragma GCC diagnostic pop
882 default:
883 break;
884 }
885 return false;
886}
887
889{
890 if (!p)
891 {
892 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid NetStreamRequest");
893 return false;
894 }
895
896 if (!p->m_bCancelled)
897 {
898 QNetworkReply *reply = m_nam->get(p->m_req);
899 LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) StartRequest 0x%2")
900 .arg(p->m_id).arg(quintptr(reply),0,16) );
901 emit requestStarted(p->m_id, reply);
902 }
903 else
904 {
905 LOG(VB_FILE, LOG_INFO, LOC + QString("(%1) NetStreamRequest cancelled").arg(p->m_id) );
906 }
907 return true;
908}
909
911{
912 if (!p)
913 {
914 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid NetStreamAbort");
915 return false;
916 }
917
918 LOG(VB_FILE, LOG_DEBUG, LOC + QString("(%1) AbortRequest 0x%2").arg(p->m_id)
919 .arg(quintptr(p->m_reply),0,16) );
920 p->m_reply->abort();
921 p->m_reply->disconnect();
922 delete p->m_reply;
923 return true;
924}
925
926// static
928{
929 auto interfaces = QNetworkInterface::allInterfaces();
930 return std::ranges::any_of(interfaces,
931 [](const QNetworkInterface& iface)
932 {
933 auto f = iface.flags();
934 if (f.testFlag(QNetworkInterface::IsLoopBack))
935 return false;
936 return f.testFlag(QNetworkInterface::IsRunning);
937 } );
938}
939
940// Time when URI was last written to cache or invalid if not cached.
941// static
942QDateTime NAMThread::GetLastModified(const QUrl &url)
943{
944 NAMThread &m = manager();
945
946 QMutexLocker locker(&m.m_mutex);
947
948 if (!m.m_nam)
949 return {}; // Invalid
950
951 QAbstractNetworkCache *cache = m.m_nam->cache();
952 if (!cache)
953 return {}; // Invalid
954
955 QNetworkCacheMetaData meta = cache->metaData(url);
956 if (!meta.isValid())
957 {
958 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') not in cache")
959 .arg(url.toString()));
960 return {}; // Invalid
961 }
962
963 // Check if expired
964 QDateTime const now(QDateTime::currentDateTime()); // local time
965 QDateTime expire = meta.expirationDate();
966 if (expire.isValid() && expire.toLocalTime() < now)
967 {
968 LOG(VB_FILE, LOG_INFO, LOC + QString("GetLastModified('%1') past expiration %2")
969 .arg(url.toString(), expire.toString()));
970 return {}; // Invalid
971 }
972
973 // Get time URI was modified (Last-Modified header) NB this may be invalid
974 QDateTime lastMod = meta.lastModified();
975
976 QNetworkCacheMetaData::RawHeaderList headers = meta.rawHeaders();
977 for (const auto& h : std::as_const(headers))
978 {
979 // RFC 1123 date format: Thu, 01 Dec 1994 16:00:00 GMT
980 static const QString kSzFormat { "ddd, dd MMM yyyy HH:mm:ss 'GMT'" };
981
982 QString const first(h.first.toLower());
983 if (first == "cache-control")
984 {
985 QString const second(h.second.toLower());
986 if (second == "no-cache" || second == "no-store")
987 {
988 LOG(VB_FILE, LOG_INFO, LOC +
989 QString("GetLastModified('%1') Cache-Control disabled")
990 .arg(url.toString()) );
991 cache->remove(url);
992 return {}; // Invalid
993 }
994 }
995 else if (first == "date")
996 {
997 QDateTime d = QDateTime::fromString(h.second, kSzFormat);
998 if (!d.isValid())
999 {
1000 LOG(VB_GENERAL, LOG_WARNING, LOC +
1001 QString("GetLastModified invalid Date header '%1'")
1002 .arg(h.second.constData()));
1003 continue;
1004 }
1005#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1006 d.setTimeSpec(Qt::UTC);
1007#else
1008 d.setTimeZone(QTimeZone(QTimeZone::UTC));
1009#endif
1010 lastMod = d;
1011 }
1012 }
1013
1014 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1') last modified %2")
1015 .arg(url.toString(), lastMod.toString()));
1016 return lastMod;
1017}
1018
1019/* 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:927
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:888
static QRecursiveMutex * GetMutex()
Definition: netstream.h:129
QSemaphore m_running
Definition: netstream.h:151
void Post(QEvent *event)
Definition: netstream.cpp:865
volatile bool m_bQuit
Definition: netstream.h:150
QQueue< QEvent * > m_workQ
Definition: netstream.h:155
bool NewRequest(QEvent *event)
Definition: netstream.cpp:871
static bool AbortRequest(NetStreamAbort *p)
Definition: netstream.cpp:910
~NAMThread() override
Definition: netstream.cpp:769
static NAMThread & manager()
NetworkAccessManager event loop thread.
Definition: netstream.cpp:747
void requestStarted(int, QNetworkReply *)
static QDateTime GetLastModified(const QUrl &url)
Definition: netstream.cpp:942
void quit()
Definition: netstream.cpp:859
QMutex m_mutex
Definition: netstream.h:154
void run() override
Definition: netstream.cpp:776
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:687
qlonglong GetSize() const
Definition: netstream.cpp:630
void slotRequestStarted(int id, QNetworkReply *reply)
Definition: netstream.cpp:281
~NetStream() override
Definition: netstream.cpp:124
qlonglong Seek(qlonglong pos)
Definition: netstream.cpp:602
QNetworkReply * m_reply
Definition: netstream.h:102
void slotReadyRead()
Definition: netstream.cpp:369
bool isReady() const
Definition: netstream.cpp:714
static bool isAvailable()
Public helpers.
Definition: netstream.cpp:730
int safe_read(void *data, unsigned sz, unsigned millisecs=0)
Definition: netstream.cpp:570
NetStreamRequest * m_pending
Definition: netstream.h:101
bool IsOpen() const
Definition: netstream.cpp:539
@ kNeverCache
Definition: netstream.h:36
@ kPreferCache
Definition: netstream.h:36
@ kAlwaysCache
Definition: netstream.h:36
QByteArray ReadAll()
Definition: netstream.cpp:693
QNetworkReply::NetworkError GetError() const
Definition: netstream.cpp:675
static QDateTime GetLastModified(const QUrl &url)
Definition: netstream.cpp:737
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:489
void Finished(QObject *)
bool WaitTillReady(std::chrono::milliseconds timeout)
Synchronous interface.
Definition: netstream.cpp:641
bool isStarted() const
Asynchronous interface.
Definition: netstream.cpp:708
static bool IsSupported(const QUrl &url)
RingBuffer interface.
Definition: netstream.cpp:531
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:623
QNetworkRequest m_request
Definition: netstream.h:98
const int m_id
Definition: netstream.h:94
void Abort()
Definition: netstream.cpp:545
void slotFinished()
Definition: netstream.cpp:420
bool isFinished() const
Definition: netstream.cpp:720
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:681
bool WaitTillFinished(std::chrono::milliseconds timeout)
Definition: netstream.cpp:658
static const iso6937table * d
@ quit
Definition: lirc_client.h:34
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetShareDir(void)
Definition: mythdirs.cpp:283
QString GetConfDir(void)
Definition: mythdirs.cpp:285
#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
bool exists(str path)
Definition: xbmcvfs.py:51
static qlonglong ContentLength(const QNetworkReply *reply)
Definition: netstream.cpp:319
static QAtomicInt s_nRequest(1)
static qlonglong ContentRange(const QNetworkReply *reply, qulonglong &first, qulonglong &last)
Definition: netstream.cpp:327
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