MythTV master
mythdownloadmanager.cpp
Go to the documentation of this file.
1// qt
2#include <QtGlobal>
3#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
4#include <QtSystemDetection>
5#endif
6#include <QCoreApplication>
7#include <QRunnable>
8#include <QString>
9#include <QByteArray>
10#include <QFile>
11#include <QDir>
12#include <QNetworkCookie>
13#include <QAuthenticator>
14#include <QTextStream>
15#include <QTimeZone>
16#include <QNetworkProxy>
17#include <QMutexLocker>
18#include <QUrl>
19#include <QTcpSocket>
20
21#include <cstdlib>
22#include <thread>
23
24// libmythbase
25#include "compat.h"
26#include "mythcorecontext.h"
27#include "mthreadpool.h"
28#include "mythdirs.h"
29#include "mythevent.h"
30#include "mythversion.h"
31#include "remotefile.h"
32#include "mythdate.h"
33
34#include "mythdownloadmanager.h"
35#include "mythlogging.h"
36#include "portchecker.h"
37
38#define LOC QString("DownloadManager: ")
39static constexpr int CACHE_REDIRECTION_LIMIT { 10 };
40
43
48{
49 public:
52 {
53 qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
54 }
55
57 {
58 delete m_request;
60 m_reply->deleteLater();
61 }
62
63 bool IsDone(void)
64 {
65 QMutexLocker lock(&m_lock);
66 return m_done;
67 }
68
69 void SetDone(bool done)
70 {
71 QMutexLocker lock(&m_lock);
72 m_done = done;
73 }
74
75 QString m_url;
77 QString *m_finalUrl {nullptr};
78 QNetworkRequest *m_request {nullptr};
79 QNetworkReply *m_reply {nullptr};
80 QString m_outFile;
81 QByteArray *m_data {nullptr};
82 QByteArray m_privData;
83 QObject *m_caller {nullptr};
85 bool m_reload {false};
86 bool m_preferCache {false};
87 bool m_syncMode {false};
88 bool m_processReply {true};
89 bool m_done {false};
90 qint64 m_bytesReceived {0};
91 qint64 m_bytesTotal {0};
92 QDateTime m_lastStat;
94 void *m_authArg {nullptr};
95 const QHash<QByteArray, QByteArray> *m_headers {nullptr};
96
97 QNetworkReply::NetworkError m_errorCode {QNetworkReply::NoError};
98 QMutex m_lock;
99};
100
101
105class RemoteFileDownloadThread : public QRunnable
106{
107 public:
109 MythDownloadInfo *dlInfo) :
110 m_parent(parent),
111 m_dlInfo(dlInfo) {}
112
113 void run() override // QRunnable
114 {
115 bool ok = false;
116
117 auto *rf = new RemoteFile(m_dlInfo->m_url, false, false, 0ms);
118 ok = rf->SaveAs(m_dlInfo->m_privData);
119 delete rf;
120
121 if (!ok)
122 m_dlInfo->m_errorCode = QNetworkReply::UnknownNetworkError;
123
126
128 }
129
130 private:
133};
134
138{
139 if (downloadManager)
140 {
141 delete downloadManager;
142 downloadManager = nullptr;
143 }
144}
145
150{
151 if (downloadManager)
152 return downloadManager;
153
154 QMutexLocker locker(&dmCreateLock);
155
156 // Check once more in case the download manager was created
157 // while we were securing the lock.
158 if (downloadManager)
159 return downloadManager;
160
161 auto *tmpDLM = new MythDownloadManager();
162 tmpDLM->start();
163 while (!tmpDLM->getQueueThread())
164 std::this_thread::sleep_for(10ms);
165
166 tmpDLM->moveToThread(tmpDLM->getQueueThread());
167 tmpDLM->setRunThread();
168
169 while (!tmpDLM->isRunning())
170 std::this_thread::sleep_for(10ms);
171
172 downloadManager = tmpDLM;
173
175
176 return downloadManager;
177}
178
182{
183 m_runThread = false;
184 m_queueWaitCond.wakeAll();
185
186 wait();
187
188 delete m_infoLock;
189 delete m_inCookieJar;
190}
191
196{
197 RunProlog();
198
199 bool downloading = false;
200 bool itemsInQueue = false;
201 bool itemsInCancellationQueue = false;
202 bool waitAnyway = false;
203
204 m_queueThread = QThread::currentThread();
205
206 while (!m_runThread)
207 std::this_thread::sleep_for(50ms);
208
209 m_manager = new QNetworkAccessManager(this);
210 m_diskCache = new QNetworkDiskCache(this);
211 m_proxy = new QNetworkProxy();
212 m_diskCache->setCacheDirectory(GetConfDir() + "/cache/" +
213 QCoreApplication::applicationName() + "-" +
215 m_manager->setCache(m_diskCache);
216
217 // Set the proxy for the manager to be the application default proxy,
218 // which has already been setup
219 m_manager->setProxy(*m_proxy);
220
221 // make sure the cookieJar is created in the same thread as the manager
222 // and set its parent to nullptr so it can be shared between managers
223 m_manager->cookieJar()->setParent(nullptr);
224
225 QObject::connect(m_manager, &QNetworkAccessManager::finished,
226 this, qOverload<QNetworkReply*>(&MythDownloadManager::downloadFinished));
227
228 m_isRunning = true;
229 while (m_runThread)
230 {
231 if (m_inCookieJar)
232 {
233 LOG(VB_GENERAL, LOG_DEBUG, "Updating DLManager's Cookie Jar");
235 }
236 m_infoLock->lock();
237 LOG(VB_FILE, LOG_DEBUG, LOC + QString("items downloading %1").arg(m_downloadInfos.count()));
238 LOG(VB_FILE, LOG_DEBUG, LOC + QString("items queued %1").arg(m_downloadQueue.count()));
239 downloading = !m_downloadInfos.isEmpty();
240 itemsInCancellationQueue = !m_cancellationQueue.isEmpty();
241 m_infoLock->unlock();
242
243 if (itemsInCancellationQueue)
244 {
246 }
247 if (downloading)
248 QCoreApplication::processEvents();
249
250 m_infoLock->lock();
251 itemsInQueue = !m_downloadQueue.isEmpty();
252 m_infoLock->unlock();
253
254 if (!itemsInQueue || waitAnyway)
255 {
256 waitAnyway = false;
257 m_queueWaitLock.lock();
258
259 if (downloading)
260 {
261 LOG(VB_FILE, LOG_DEBUG, LOC + QString("waiting 200ms"));
263 }
264 else
265 {
266 LOG(VB_FILE, LOG_DEBUG, LOC + QString("waiting for more items to download"));
268 }
269
270 m_queueWaitLock.unlock();
271 }
272
273 m_infoLock->lock();
274 if (!m_downloadQueue.isEmpty())
275 {
276 MythDownloadInfo *dlInfo = m_downloadQueue.front();
277
278 m_downloadQueue.pop_front();
279
280 if (!dlInfo)
281 {
282 m_infoLock->unlock();
283 continue;
284 }
285
286 if (m_downloadInfos.contains(dlInfo->m_url))
287 {
288 // Push request to the end of the queue to let others process.
289 // If this is the only item in the queue, force the loop to
290 // wait a little.
291 if (m_downloadQueue.isEmpty())
292 waitAnyway = true;
293 m_downloadQueue.push_back(dlInfo);
294 m_infoLock->unlock();
295 continue;
296 }
297
298 if (dlInfo->m_url.startsWith("myth://"))
299 {
300 downloadRemoteFile(dlInfo);
301 }
302 else
303 {
304 QMutexLocker cLock(&m_cookieLock);
306 }
307
308 m_downloadInfos[dlInfo->m_url] = dlInfo;
309 }
310 m_infoLock->unlock();
311 }
312 m_isRunning = false;
313
314 RunEpilog();
315}
316
327void MythDownloadManager::queueItem(const QString &url, QNetworkRequest *req,
328 const QString &dest, QByteArray *data,
329 QObject *caller, const MRequestType reqType,
330 const bool reload)
331{
332 auto *dlInfo = new MythDownloadInfo;
333
334 dlInfo->m_url = url;
335 dlInfo->m_request = req;
336 dlInfo->m_outFile = dest;
337 dlInfo->m_data = data;
338 dlInfo->m_caller = caller;
339 dlInfo->m_requestType = reqType;
340 dlInfo->m_reload = reload;
341
342 QMutexLocker locker(m_infoLock);
343 m_downloadQueue.push_back(dlInfo);
344 m_queueWaitCond.wakeAll();
345}
346
359bool MythDownloadManager::processItem(const QString &url, QNetworkRequest *req,
360 const QString &dest, QByteArray *data,
361 const MRequestType reqType,
362 const bool reload,
363 AuthCallback authCallbackFn, void *authArg,
364 const QHash<QByteArray, QByteArray> *headers,
365 QString *finalUrl)
366{
367 auto *dlInfo = new MythDownloadInfo;
368
369 dlInfo->m_url = url;
370 dlInfo->m_request = req;
371 dlInfo->m_outFile = dest;
372 dlInfo->m_data = data;
373 dlInfo->m_requestType = reqType;
374 dlInfo->m_reload = reload;
375 dlInfo->m_syncMode = true;
376 dlInfo->m_authCallback = authCallbackFn;
377 dlInfo->m_authArg = authArg;
378 dlInfo->m_headers = headers;
379 dlInfo->m_finalUrl = finalUrl;
380
381 return downloadNow(dlInfo, true);
382}
383
387void MythDownloadManager::preCache(const QString &url)
388{
389 LOG(VB_FILE, LOG_DEBUG, LOC + QString("preCache('%1')").arg(url));
390 queueItem(url, nullptr, QString(), nullptr, nullptr);
391}
392
400 const QString &dest,
401 QObject *caller,
402 const bool reload)
403{
404 LOG(VB_FILE, LOG_DEBUG, LOC + QString("queueDownload('%1', '%2', %3)")
405 .arg(url, dest, QString::number((long long)caller)));
406
407 queueItem(url, nullptr, dest, nullptr, caller, kRequestGet, reload);
408}
409
415void MythDownloadManager::queueDownload(QNetworkRequest *req,
416 QByteArray *data,
417 QObject *caller)
418{
419 LOG(VB_FILE, LOG_DEBUG, LOC + QString("queueDownload('%1', '%2', %3)")
420 .arg(req->url().toString()).arg((long long)data)
421 .arg((long long)caller));
422
423 queueItem(req->url().toString(), req, QString(), data, caller,
425 (QNetworkRequest::AlwaysNetwork == req->attribute(
426 QNetworkRequest::CacheLoadControlAttribute,
427 QNetworkRequest::PreferNetwork).toInt()));
428}
429
436bool MythDownloadManager::download(const QString &url, const QString &dest,
437 const bool reload)
438{
439 return processItem(url, nullptr, dest, nullptr, kRequestGet, reload);
440}
441
448bool MythDownloadManager::download(const QString &url, QByteArray *data,
449 const bool reload, QString *finalUrl)
450{
451 QString redirected;
452 if (!processItem(url, nullptr, QString(), data, kRequestGet, reload,
453 nullptr, nullptr, nullptr, &redirected))
454 return false;
455 if (!redirected.isEmpty() && finalUrl != nullptr)
456 *finalUrl = redirected;
457 return true;
458}
459
466QNetworkReply *MythDownloadManager::download(const QString &url,
467 const bool reload)
468{
469 auto *dlInfo = new MythDownloadInfo;
470 QNetworkReply *reply = nullptr;
471
472 dlInfo->m_url = url;
473 dlInfo->m_reload = reload;
474 dlInfo->m_syncMode = true;
475 dlInfo->m_processReply = false;
476
477 if (downloadNow(dlInfo, false))
478 {
479 if (dlInfo->m_reply)
480 {
481 reply = dlInfo->m_reply;
482 // prevent dlInfo dtor from deleting the reply
483 dlInfo->m_reply = nullptr;
484
485 delete dlInfo;
486
487 return reply;
488 }
489
490 delete dlInfo;
491 }
492
493 return nullptr;
494}
495
501bool MythDownloadManager::download(QNetworkRequest *req, QByteArray *data)
502{
503 LOG(VB_FILE, LOG_DEBUG, LOC + QString("download('%1', '%2')")
504 .arg(req->url().toString()).arg((long long)data));
505 return processItem(req->url().toString(), req, QString(), data,
507 (QNetworkRequest::AlwaysNetwork == req->attribute(
508 QNetworkRequest::CacheLoadControlAttribute,
509 QNetworkRequest::PreferNetwork).toInt()));
510}
511
521bool MythDownloadManager::downloadAuth(const QString &url, const QString &dest,
522 const bool reload, AuthCallback authCallbackFn, void *authArg,
523 const QHash<QByteArray, QByteArray> *headers)
524{
525 return processItem(url, nullptr, dest, nullptr, kRequestGet, reload, authCallbackFn,
526 authArg, headers);
527}
528
529
535void MythDownloadManager::queuePost(const QString &url,
536 QByteArray *data,
537 QObject *caller)
538{
539 LOG(VB_FILE, LOG_DEBUG, LOC + QString("queuePost('%1', '%2')")
540 .arg(url).arg((long long)data));
541
542 if (!data)
543 {
544 LOG(VB_GENERAL, LOG_ERR, LOC + "queuePost(), data is NULL!");
545 return;
546 }
547
548 queueItem(url, nullptr, QString(), data, caller, kRequestPost);
549}
550
556void MythDownloadManager::queuePost(QNetworkRequest *req,
557 QByteArray *data,
558 QObject *caller)
559{
560 LOG(VB_FILE, LOG_DEBUG, LOC + QString("queuePost('%1', '%2')")
561 .arg(req->url().toString()).arg((long long)data));
562
563 if (!data)
564 {
565 LOG(VB_GENERAL, LOG_ERR, LOC + "queuePost(), data is NULL!");
566 return;
567 }
568
569 queueItem(req->url().toString(), req, QString(), data, caller,
571 (QNetworkRequest::AlwaysNetwork == req->attribute(
572 QNetworkRequest::CacheLoadControlAttribute,
573 QNetworkRequest::PreferNetwork).toInt()));
574
575}
576
582bool MythDownloadManager::post(const QString &url, QByteArray *data)
583{
584 LOG(VB_FILE, LOG_DEBUG, LOC + QString("post('%1', '%2')")
585 .arg(url).arg((long long)data));
586
587 if (!data)
588 {
589 LOG(VB_GENERAL, LOG_ERR, LOC + "post(), data is NULL!");
590 return false;
591 }
592
593 return processItem(url, nullptr, QString(), data, kRequestPost);
594}
595
601bool MythDownloadManager::post(QNetworkRequest *req, QByteArray *data)
602{
603 LOG(VB_FILE, LOG_DEBUG, LOC + QString("post('%1', '%2')")
604 .arg(req->url().toString()).arg((long long)data));
605
606 if (!data)
607 {
608 LOG(VB_GENERAL, LOG_ERR, LOC + "post(), data is NULL!");
609 return false;
610 }
611
612 return processItem(req->url().toString(), req, QString(), data,
614 (QNetworkRequest::AlwaysNetwork == req->attribute(
615 QNetworkRequest::CacheLoadControlAttribute,
616 QNetworkRequest::PreferNetwork).toInt()));
617
618}
619
628bool MythDownloadManager::postAuth(const QString &url, QByteArray *data,
629 AuthCallback authCallbackFn, void *authArg,
630 const QHash<QByteArray, QByteArray> *headers)
631{
632 LOG(VB_FILE, LOG_DEBUG, LOC + QString("postAuth('%1', '%2')")
633 .arg(url).arg((long long)data));
634
635 if (!data)
636 {
637 LOG(VB_GENERAL, LOG_ERR, LOC + "postAuth(), data is NULL!");
638 return false;
639 }
640
641 return processItem(url, nullptr, nullptr, data, kRequestPost, false, authCallbackFn,
642 authArg, headers);
643}
644
649{
650 auto *dlThread = new RemoteFileDownloadThread(this, dlInfo);
651 MThreadPool::globalInstance()->start(dlThread, "RemoteFileDownload");
652}
653
658{
659 if (!dlInfo)
660 return;
661
662 static const QString kDateFormat = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
663 QUrl qurl(dlInfo->m_url);
664 QNetworkRequest request;
665
666 if (dlInfo->m_request)
667 {
668 request = *dlInfo->m_request;
669 delete dlInfo->m_request;
670 dlInfo->m_request = nullptr;
671 }
672 else
673 {
674 request.setUrl(qurl);
675 }
676
677 if (dlInfo->m_reload)
678 {
679 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
680 QNetworkRequest::AlwaysNetwork);
681 }
682 else
683 {
684 // Prefer the in-cache item if one exists and it is less than 5 minutes
685 // old and it will not expire in the next 10 seconds
686 QDateTime now = MythDate::current();
687
688 // Handle redirects, we want the metadata of the file headers
689 QString redirectLoc;
690 int limit = 0;
691 while (!(redirectLoc = getHeader(qurl, "Location")).isNull())
692 {
693 if (limit == CACHE_REDIRECTION_LIMIT)
694 {
695 LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
696 "reached for %1")
697 .arg(qurl.toString()));
698 return;
699 }
700 qurl.setUrl(redirectLoc);
701 limit++;
702 }
703
704 LOG(VB_NETWORK, LOG_DEBUG, QString("Checking cache for %1")
705 .arg(qurl.toString()));
706
707 m_infoLock->lock();
708 QNetworkCacheMetaData urlData = m_manager->cache()->metaData(qurl);
709 m_infoLock->unlock();
710 if ((urlData.isValid()) &&
711 ((!urlData.expirationDate().isValid()) ||
712 (urlData.expirationDate().toUTC().secsTo(now) < 10)))
713 {
714 QString dateString = getHeader(urlData, "Date");
715
716 if (!dateString.isNull())
717 {
718 QDateTime loadDate =
719 MythDate::fromString(dateString, kDateFormat);
720#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
721 loadDate.setTimeSpec(Qt::UTC);
722#else
723 loadDate.setTimeZone(QTimeZone(QTimeZone::UTC));
724#endif
725 if (loadDate.secsTo(now) <= 720)
726 {
727 dlInfo->m_preferCache = true;
728 LOG(VB_NETWORK, LOG_DEBUG, QString("Preferring cache for %1")
729 .arg(qurl.toString()));
730 }
731 }
732 }
733 }
734
735 if (dlInfo->m_preferCache)
736 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
737 QNetworkRequest::PreferCache);
738
739 if (!request.hasRawHeader("User-Agent"))
740 {
741 request.setRawHeader("User-Agent",
742 QByteArray("MythTV v") + MYTH_BINARY_VERSION +
743 " MythDownloadManager");
744 }
745
746 if (dlInfo->m_headers)
747 {
748 QHash<QByteArray, QByteArray>::const_iterator it =
749 dlInfo->m_headers->constBegin();
750 for ( ; it != dlInfo->m_headers->constEnd(); ++it )
751 {
752 if (!it.key().isEmpty() && !it.value().isEmpty())
753 {
754 request.setRawHeader(it.key(), it.value());
755 }
756 }
757 }
758
759 switch (dlInfo->m_requestType)
760 {
761 case kRequestPost :
762 dlInfo->m_reply = m_manager->post(request, *dlInfo->m_data);
763 break;
764 case kRequestHead :
765 dlInfo->m_reply = m_manager->head(request);
766 break;
767 case kRequestGet :
768 default:
769 dlInfo->m_reply = m_manager->get(request);
770 break;
771 }
772
773 m_downloadReplies[dlInfo->m_reply] = dlInfo;
774
775 if (dlInfo->m_authCallback)
776 {
777 connect(m_manager, &QNetworkAccessManager::authenticationRequired,
779 }
780
781 connect(dlInfo->m_reply, &QNetworkReply::errorOccurred,
783 connect(dlInfo->m_reply, &QNetworkReply::downloadProgress,
785}
786
791void MythDownloadManager::authCallback(QNetworkReply *reply,
792 QAuthenticator *authenticator)
793{
794 if (!reply)
795 return;
796
797 MythDownloadInfo *dlInfo = m_downloadReplies[reply];
798
799 if (!dlInfo)
800 return;
801
802 if (dlInfo->m_authCallback)
803 {
804 LOG(VB_FILE, LOG_DEBUG, "Calling auth callback");
805 dlInfo->m_authCallback(reply, authenticator, dlInfo->m_authArg);
806 }
807}
808
816{
817 if (!dlInfo)
818 return false;
819
820 dlInfo->m_syncMode = true;
821
822 // Special handling for link-local
823 // Not needed for Windows because windows does not need
824 // the scope id.
825#ifndef Q_OS_WINDOWS
826 if (dlInfo->m_url.startsWith("http://[fe80::",Qt::CaseInsensitive))
827 return downloadNowLinkLocal(dlInfo, deleteInfo);
828#endif
829 m_infoLock->lock();
830 m_downloadQueue.push_back(dlInfo);
831 m_infoLock->unlock();
832 m_queueWaitCond.wakeAll();
833
834 // timeout myth:// RemoteFile transfers 20 seconds from now
835 // timeout non-myth:// QNetworkAccessManager transfers 60 seconds after
836 // their last progress update
837 QDateTime startedAt = MythDate::current();
838 m_infoLock->lock();
839 while ((!dlInfo->IsDone()) &&
840 (dlInfo->m_errorCode == QNetworkReply::NoError) &&
841 (((!dlInfo->m_url.startsWith("myth://")) &&
842 (MythDate::secsInPast(dlInfo->m_lastStat) < 60s)) ||
843 ((dlInfo->m_url.startsWith("myth://")) &&
844 (MythDate::secsInPast(startedAt) < 20s))))
845 {
846 m_infoLock->unlock();
847 m_queueWaitLock.lock();
849 m_queueWaitLock.unlock();
850 m_infoLock->lock();
851 }
852 bool done = dlInfo->IsDone();
853 bool success =
854 done && (dlInfo->m_errorCode == QNetworkReply::NoError);
855
856 if (!done)
857 {
858 dlInfo->m_data = nullptr; // Prevent downloadFinished() from updating
859 dlInfo->m_syncMode = false; // Let downloadFinished() cleanup for us
860 if ((dlInfo->m_reply) &&
861 (dlInfo->m_errorCode == QNetworkReply::NoError))
862 {
863 LOG(VB_FILE, LOG_DEBUG,
864 LOC + QString("Aborting download - lack of data transfer"));
865 dlInfo->m_reply->abort();
866 }
867 }
868 else if (deleteInfo)
869 {
870 delete dlInfo;
871 }
872
873 m_infoLock->unlock();
874
875 return success;
876}
877
878#ifndef Q_OS_WINDOWS
899bool MythDownloadManager::downloadNowLinkLocal(MythDownloadInfo *dlInfo, bool deleteInfo)
900{
901 bool ok = true;
902
903 // No buffer - no reply...
904 if (!dlInfo->m_data)
905 {
906 LOG(VB_GENERAL, LOG_ERR, LOC + QString("No data buffer provided for %1").arg(dlInfo->m_url));
907 ok = false;
908 }
909
910 // Only certain features are supported here
911 if (dlInfo->m_authCallback || dlInfo->m_authArg)
912 {
913 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unsupported authentication for %1").arg(dlInfo->m_url));
914 ok = false;
915 }
916
917 if (ok && !dlInfo->m_outFile.isEmpty())
918 {
919 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unsupported File output %1 for %2")
920 .arg(dlInfo->m_outFile, dlInfo->m_url));
921 ok = false;
922 }
923
924 if (ok && (!deleteInfo || dlInfo->m_requestType == kRequestHead))
925 {
926 // We do not have the ability to return a network reply in dlInfo
927 // so if we are asked to do that, return an error.
928 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Unsupported link-local operation %1")
929 .arg(dlInfo->m_url));
930 ok = false;
931 }
932
933 QUrl url(dlInfo->m_url);
934 QString host(url.host());
935 int port(url.port(80));
936 if (ok && PortChecker{}.resolveLinkLocal(host, port))
937 {
938 QString reqType;
939 switch (dlInfo->m_requestType)
940 {
941 case kRequestPost :
942 reqType = "POST";
943 break;
944 case kRequestGet :
945 default:
946 reqType = "GET";
947 break;
948 }
949 QByteArray* buffer = dlInfo->m_data;
950 QHash<QByteArray, QByteArray> headers;
951 if (dlInfo->m_headers)
952 headers = *dlInfo->m_headers;
953 if (!headers.contains("User-Agent"))
954 headers.insert("User-Agent", QByteArray("MythDownloadManager v") +
955 MYTH_BINARY_VERSION);
956 headers.insert("Connection", "close");
957 headers.insert("Accept-Encoding", "identity");
958 if (!buffer->isEmpty())
959 headers.insert("Content-Length", QString::number(buffer->size()).toUtf8());
960 headers.insert("Host", (url.host() + ":" + QString::number(port)).toUtf8());
961
962 QByteArray requestMessage;
963 QString path (url.path());
964 requestMessage.append("POST ");
965 requestMessage.append(path.toLatin1());
966 requestMessage.append(" HTTP/1.1\r\n");
967 for (auto it = headers.cbegin(); it != headers.cend(); ++it)
968 {
969 requestMessage.append(it.key());
970 requestMessage.append(": ");
971 requestMessage.append(it.value());
972 requestMessage.append("\r\n");
973 }
974 requestMessage.append("\r\n");
975 if (!buffer->isEmpty())
976 requestMessage.append(*buffer);
977
978 QTcpSocket socket;
979 socket.connectToHost(host, static_cast<uint16_t>(port));
980 // QT Warning - this may not work on Windows
981 if (!socket.waitForConnected(5000))
982 ok = false;
983 if (ok)
984 ok = socket.write(requestMessage) > 0;
985 if (ok)
986 // QT Warning - this may not work on Windows
987 ok = socket.waitForDisconnected(5000);
988 if (ok)
989 {
990 *buffer = socket.readAll();
991 // Find the start of the content
992 QByteArray delim("\r\n\r\n");
993 int delimLoc = buffer->indexOf(delim);
994 if (delimLoc > -1)
995 *buffer = buffer->right(buffer->size() - delimLoc - 4);
996 else
997 ok=false;
998 }
999 socket.close();
1000 }
1001 else
1002 {
1003 ok = false;
1004 }
1005
1006 if (deleteInfo)
1007 delete dlInfo;
1008
1009 if (ok)
1010 return true;
1011
1012 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Link Local request failed: %1").arg(url.toString()));
1013 return false;
1014}
1015#endif
1016
1021void MythDownloadManager::cancelDownload(const QString &url, bool block)
1022{
1023 cancelDownload(QStringList(url), block);
1024}
1025
1030void MythDownloadManager::cancelDownload(const QStringList &urls, bool block)
1031{
1032 m_infoLock->lock();
1033 for (const auto& url : std::as_const(urls))
1034 {
1035 for (auto lit = m_downloadQueue.begin();
1036 lit != m_downloadQueue.end();
1037 /* no inc */)
1038 {
1039 MythDownloadInfo *dlInfo = *lit;
1040 if (dlInfo->m_url == url)
1041 {
1042 if (!m_cancellationQueue.contains(dlInfo))
1043 m_cancellationQueue.append(dlInfo);
1044 lit = m_downloadQueue.erase(lit);
1045 }
1046 else
1047 {
1048 ++lit;
1049 }
1050 }
1051
1052 if (m_downloadInfos.contains(url))
1053 {
1054 MythDownloadInfo *dlInfo = m_downloadInfos[url];
1055
1056 if (!m_cancellationQueue.contains(dlInfo))
1057 m_cancellationQueue.append(dlInfo);
1058
1059 if (dlInfo->m_reply)
1060 m_downloadReplies.remove(dlInfo->m_reply);
1061
1062 m_downloadInfos.remove(url);
1063 }
1064 }
1065 m_infoLock->unlock();
1066
1067 if (QThread::currentThread() == this->thread())
1068 {
1070 return;
1071 }
1072
1073 // wake-up running thread
1074 m_queueWaitCond.wakeAll();
1075
1076 if (!block)
1077 return;
1078
1079 while (!m_cancellationQueue.isEmpty())
1080 {
1081 std::this_thread::sleep_for(50ms); // re-test in another 50ms
1082 }
1083}
1084
1086{
1087 QMutexLocker locker(m_infoLock);
1088
1089 for (auto lit = m_cancellationQueue.begin();
1090 lit != m_cancellationQueue.end();
1091 /* no inc */)
1092 {
1093 MythDownloadInfo *dlInfo = *lit;
1094 dlInfo->m_lock.lock();
1095
1096 if (dlInfo->m_reply)
1097 {
1098 LOG(VB_FILE, LOG_DEBUG,
1099 LOC + QString("Aborting download - user request"));
1100 dlInfo->m_reply->abort();
1101 }
1102 lit = m_cancellationQueue.erase(lit);
1103 if (dlInfo->m_done)
1104 {
1105 dlInfo->m_lock.unlock();
1106 continue;
1107 }
1108 dlInfo->m_errorCode = QNetworkReply::OperationCanceledError;
1109 dlInfo->m_done = true;
1110 dlInfo->m_lock.unlock();
1111 }
1112}
1113
1119{
1120 QMutexLocker locker(m_infoLock);
1121
1122 QList <MythDownloadInfo*>::iterator lit = m_downloadQueue.begin();
1123 for (; lit != m_downloadQueue.end(); ++lit)
1124 {
1125 MythDownloadInfo *dlInfo = *lit;
1126 if (dlInfo->m_caller == caller)
1127 {
1128 dlInfo->m_caller = nullptr;
1129 dlInfo->m_outFile = QString();
1130 dlInfo->m_data = nullptr;
1131 }
1132 }
1133
1134 QMap <QString, MythDownloadInfo*>::iterator mit = m_downloadInfos.begin();
1135 for (; mit != m_downloadInfos.end(); ++mit)
1136 {
1137 MythDownloadInfo *dlInfo = mit.value();
1138 if (dlInfo->m_caller == caller)
1139 {
1140 dlInfo->m_caller = nullptr;
1141 dlInfo->m_outFile = QString();
1142 dlInfo->m_data = nullptr;
1143 }
1144 }
1145}
1146
1150void MythDownloadManager::downloadError(QNetworkReply::NetworkError errorCode)
1151{
1152 auto *reply = qobject_cast<QNetworkReply *>(sender());
1153 if (reply == nullptr)
1154 return;
1155
1156 LOG(VB_FILE, LOG_DEBUG, LOC + QString("downloadError %1 ")
1157 .arg(errorCode) + reply->errorString() );
1158
1159 QMutexLocker locker(m_infoLock);
1160 if (!m_downloadReplies.contains(reply))
1161 {
1162 reply->deleteLater();
1163 return;
1164 }
1165
1166 MythDownloadInfo *dlInfo = m_downloadReplies[reply];
1167
1168 if (!dlInfo)
1169 return;
1170
1171 dlInfo->m_errorCode = errorCode;
1172}
1173
1179QUrl MythDownloadManager::redirectUrl(const QUrl& possibleRedirectUrl,
1180 const QUrl& oldRedirectUrl)
1181{
1182 LOG(VB_FILE, LOG_DEBUG, LOC + QString("redirectUrl()"));
1183 QUrl redirectUrl;
1184
1185 if(!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != oldRedirectUrl)
1186 redirectUrl = possibleRedirectUrl;
1187
1188 return redirectUrl;
1189}
1190
1195{
1196 LOG(VB_FILE, LOG_DEBUG, LOC + QString("downloadFinished(%1)")
1197 .arg((long long)reply));
1198
1199 QMutexLocker locker(m_infoLock);
1200 if (!m_downloadReplies.contains(reply))
1201 {
1202 reply->deleteLater();
1203 return;
1204 }
1205
1206 MythDownloadInfo *dlInfo = m_downloadReplies[reply];
1207
1208 if (!dlInfo || !dlInfo->m_reply)
1209 return;
1210
1211 downloadFinished(dlInfo);
1212}
1213
1218{
1219 if (!dlInfo)
1220 return;
1221
1222 int statusCode = -1;
1223 static const QString kDateFormat = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
1224 QNetworkReply *reply = dlInfo->m_reply;
1225
1226 if (reply)
1227 {
1228 QUrl possibleRedirectUrl =
1229 reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
1230
1231 if (!possibleRedirectUrl.isEmpty() &&
1232 possibleRedirectUrl.isValid() &&
1233 possibleRedirectUrl.isRelative()) // Turn relative Url to absolute
1234 possibleRedirectUrl = reply->url().resolved(possibleRedirectUrl);
1235
1236 if (!possibleRedirectUrl.isEmpty() && dlInfo->m_finalUrl != nullptr)
1237 *dlInfo->m_finalUrl = QString(possibleRedirectUrl.toString());
1238
1239 dlInfo->m_redirectedTo =
1240 redirectUrl(possibleRedirectUrl, dlInfo->m_redirectedTo);
1241
1242 QVariant status =
1243 reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
1244 if (status.isValid())
1245 statusCode = status.toInt();
1246 }
1247
1248 if(reply && !dlInfo->m_redirectedTo.isEmpty() &&
1249 ((dlInfo->m_requestType != kRequestPost) ||
1250 (statusCode == 301 || statusCode == 302 ||
1251 statusCode == 303)))
1252 {
1253 LOG(VB_FILE, LOG_DEBUG, LOC +
1254 QString("downloadFinished(%1): Redirect: %2 -> %3")
1255 .arg(QString::number((long long)dlInfo),
1256 reply->url().toString(),
1257 dlInfo->m_redirectedTo.toString()));
1258
1259 if (dlInfo->m_data)
1260 dlInfo->m_data->clear();
1261
1262 dlInfo->m_bytesReceived = 0;
1263 dlInfo->m_bytesTotal = 0;
1264
1265 QNetworkRequest request(dlInfo->m_redirectedTo);
1266
1267 if (dlInfo->m_reload)
1268 {
1269 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
1270 QNetworkRequest::AlwaysNetwork);
1271 }
1272 else if (dlInfo->m_preferCache)
1273 {
1274 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
1275 QNetworkRequest::PreferCache);
1276 }
1277
1278 request.setRawHeader("User-Agent",
1279 "MythDownloadManager v" +
1280 QByteArray(MYTH_BINARY_VERSION));
1281
1282 switch (dlInfo->m_requestType)
1283 {
1284 case kRequestHead :
1285 dlInfo->m_reply = m_manager->head(request);
1286 break;
1287 case kRequestGet :
1288 default:
1289 dlInfo->m_reply = m_manager->get(request);
1290 break;
1291 }
1292
1293 m_downloadReplies[dlInfo->m_reply] = dlInfo;
1294
1295 connect(dlInfo->m_reply, &QNetworkReply::errorOccurred,
1297 connect(dlInfo->m_reply, &QNetworkReply::downloadProgress,
1299
1300 m_downloadReplies.remove(reply);
1301 reply->deleteLater();
1302 }
1303 else
1304 {
1305 LOG(VB_FILE, LOG_DEBUG, QString("downloadFinished(%1): COMPLETE: %2")
1306 .arg((long long)dlInfo).arg(dlInfo->m_url));
1307
1308 // HACK Insert a Date header into the cached metadata if one doesn't
1309 // already exist
1310 QUrl fileUrl { dlInfo->m_url };
1311 QString redirectLoc;
1312 int limit = 0;
1313 while (!(redirectLoc = getHeader(fileUrl, "Location")).isNull())
1314 {
1315 QUrl redirUrl { redirectLoc };
1316 if (!redirUrl.isValid())
1317 {
1318 LOG(VB_GENERAL, LOG_WARNING, QString("Invalid redirect %1 for %2")
1319 .arg(redirectLoc, fileUrl.toString()));
1320 return;
1321 }
1322 if (limit == CACHE_REDIRECTION_LIMIT)
1323 {
1324 LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
1325 "reached for %1")
1326 .arg(fileUrl.toString()));
1327 return;
1328 }
1329 fileUrl.setUrl(redirectLoc);
1330 limit++;
1331 }
1332
1333 m_infoLock->lock();
1334 QNetworkCacheMetaData urlData = m_manager->cache()->metaData(fileUrl);
1335 m_infoLock->unlock();
1336 if (getHeader(urlData, "Date").isNull())
1337 {
1338 QNetworkCacheMetaData::RawHeaderList headers = urlData.rawHeaders();
1339 QNetworkCacheMetaData::RawHeader newheader;
1340 QDateTime now = MythDate::current();
1341 newheader = QNetworkCacheMetaData::RawHeader("Date",
1342 now.toString(kDateFormat).toLatin1());
1343 headers.append(newheader);
1344 urlData.setRawHeaders(headers);
1345 m_infoLock->lock();
1346 m_manager->cache()->updateMetaData(urlData);
1347 m_infoLock->unlock();
1348 }
1349 // End HACK
1350
1351 dlInfo->m_redirectedTo.clear();
1352
1353 int dataSize = -1;
1354
1355 // If we downloaded via the QNetworkAccessManager
1356 // AND the caller isn't handling the reply directly
1357 if (reply && dlInfo->m_processReply)
1358 {
1359 bool append = (!dlInfo->m_syncMode && dlInfo->m_caller);
1360 QByteArray data = reply->readAll();
1361 dataSize = data.size();
1362
1363 if (append)
1364 dlInfo->m_bytesReceived += dataSize;
1365 else
1366 dlInfo->m_bytesReceived = dataSize;
1367
1368 dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
1369
1370 if (dlInfo->m_data)
1371 {
1372 if (append)
1373 dlInfo->m_data->append(data);
1374 else
1375 *dlInfo->m_data = data;
1376 }
1377 else if (!dlInfo->m_outFile.isEmpty())
1378 {
1379 saveFile(dlInfo->m_outFile, data, append);
1380 }
1381 }
1382 else if (!reply) // If we downloaded via RemoteFile
1383 {
1384 if (dlInfo->m_data)
1385 {
1386 (*dlInfo->m_data) = dlInfo->m_privData;
1387 }
1388 else if (!dlInfo->m_outFile.isEmpty())
1389 {
1390 saveFile(dlInfo->m_outFile, dlInfo->m_privData);
1391 }
1392 dlInfo->m_bytesReceived += dataSize;
1393 dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
1394 }
1395 // else we downloaded via QNetworkAccessManager
1396 // AND the caller is handling the reply
1397
1398 m_infoLock->lock();
1399 if (!m_downloadInfos.remove(dlInfo->m_url))
1400 {
1401 LOG(VB_GENERAL, LOG_ERR, LOC +
1402 QString("ERROR download finished but failed to remove url: %1")
1403 .arg(dlInfo->m_url));
1404 }
1405
1406 if (reply)
1407 m_downloadReplies.remove(reply);
1408 m_infoLock->unlock();
1409
1410 dlInfo->SetDone(true);
1411
1412 if (!dlInfo->m_syncMode)
1413 {
1414 if (dlInfo->m_caller)
1415 {
1416 LOG(VB_FILE, LOG_DEBUG, QString("downloadFinished(%1): "
1417 "COMPLETE: %2, sending event to caller")
1418 .arg((long long)dlInfo).arg(dlInfo->m_url));
1419
1420 QStringList args;
1421 args << dlInfo->m_url;
1422 args << dlInfo->m_outFile;
1423 args << QString::number(dlInfo->m_bytesTotal);
1424 // placeholder for error string
1425 args << (reply ? reply->errorString() : QString());
1426 args << QString::number((int)(reply ? reply->error() :
1427 dlInfo->m_errorCode));
1428
1429 QCoreApplication::postEvent(dlInfo->m_caller,
1430 new MythEvent("DOWNLOAD_FILE FINISHED", args));
1431 }
1432
1433 delete dlInfo;
1434 }
1435
1436 m_queueWaitCond.wakeAll();
1437 }
1438}
1439
1446 qint64 bytesTotal)
1447{
1448 auto *reply = qobject_cast<QNetworkReply *>(sender());
1449 if (reply == nullptr)
1450 return;
1451
1452 LOG(VB_FILE, LOG_DEBUG, LOC +
1453 QString("downloadProgress(%1, %2) (for reply %3)")
1454 .arg(bytesReceived).arg(bytesTotal).arg((long long)reply));
1455
1456 QMutexLocker locker(m_infoLock);
1457 if (!m_downloadReplies.contains(reply))
1458 return;
1459
1460 MythDownloadInfo *dlInfo = m_downloadReplies[reply];
1461
1462 if (!dlInfo)
1463 return;
1464
1465 dlInfo->m_lastStat = MythDate::current();
1466
1467 LOG(VB_FILE, LOG_DEBUG, LOC +
1468 QString("downloadProgress: %1 to %2 is at %3 of %4 bytes downloaded")
1469 .arg(dlInfo->m_url, dlInfo->m_outFile)
1470 .arg(bytesReceived).arg(bytesTotal));
1471
1472 if (!dlInfo->m_syncMode && dlInfo->m_caller)
1473 {
1474 LOG(VB_FILE, LOG_DEBUG, QString("downloadProgress(%1): "
1475 "sending event to caller")
1476 .arg(reply->url().toString()));
1477
1478 bool appendToFile = (dlInfo->m_bytesReceived != 0);
1479 QByteArray data = reply->readAll();
1480 if (!dlInfo->m_outFile.isEmpty())
1481 saveFile(dlInfo->m_outFile, data, appendToFile);
1482
1483 if (dlInfo->m_data)
1484 dlInfo->m_data->append(data);
1485
1486 dlInfo->m_bytesReceived = bytesReceived;
1487 dlInfo->m_bytesTotal = bytesTotal;
1488
1489 QStringList args;
1490 args << dlInfo->m_url;
1491 args << dlInfo->m_outFile;
1492 args << QString::number(bytesReceived);
1493 args << QString::number(bytesTotal);
1494
1495 QCoreApplication::postEvent(dlInfo->m_caller,
1496 new MythEvent("DOWNLOAD_FILE UPDATE", args));
1497 }
1498}
1499
1507bool MythDownloadManager::saveFile(const QString &outFile,
1508 const QByteArray &data,
1509 const bool append)
1510{
1511 if (outFile.isEmpty() || data.isEmpty())
1512 return false;
1513
1514 QFile file(outFile);
1515 QFileInfo fileInfo(outFile);
1516 QDir qdir(fileInfo.absolutePath());
1517
1518 if (!qdir.exists() && !qdir.mkpath(fileInfo.absolutePath()))
1519 {
1520 LOG(VB_GENERAL, LOG_ERR, QString("Failed to create: '%1'")
1521 .arg(fileInfo.absolutePath()));
1522 return false;
1523 }
1524
1525 QIODevice::OpenMode mode = QIODevice::Unbuffered|QIODevice::WriteOnly;
1526 if (append)
1527 mode |= QIODevice::Append;
1528
1529 if (!file.open(mode))
1530 {
1531 LOG(VB_GENERAL, LOG_ERR, QString("Failed to open: '%1'") .arg(outFile));
1532 return false;
1533 }
1534
1535 off_t offset = 0;
1536 size_t remaining = data.size();
1537 uint failure_cnt = 0;
1538 while ((remaining > 0) && (failure_cnt < 5))
1539 {
1540 ssize_t written = file.write(data.data() + offset, remaining);
1541 if (written < 0)
1542 {
1543 failure_cnt++;
1544 std::this_thread::sleep_for(50ms);
1545 continue;
1546 }
1547
1548 failure_cnt = 0;
1549 offset += written;
1550 remaining -= written;
1551 }
1552
1553 return remaining <= 0;
1554}
1555
1560QDateTime MythDownloadManager::GetLastModified(const QString &url)
1561{
1562 // If the header has not expired and
1563 // the last modification date is less than 1 hours old or if
1564 // the cache object is less than 20 minutes old,
1565 // then use the cached header otherwise redownload the header
1566
1567 static const QString kDateFormat = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
1568 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1')").arg(url));
1569 QDateTime result;
1570
1571 QDateTime now = MythDate::current();
1572
1573 QUrl cacheUrl = QUrl(url);
1574
1575 // Deal with redirects, we want the cached data for the final url
1576 QString redirectLoc;
1577 int limit = 0;
1578 while (!(redirectLoc = getHeader(cacheUrl, "Location")).isNull())
1579 {
1580 if (limit == CACHE_REDIRECTION_LIMIT)
1581 {
1582 LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
1583 "reached for %1")
1584 .arg(cacheUrl.toString()));
1585 return result;
1586 }
1587 cacheUrl.setUrl(redirectLoc);
1588 limit++;
1589 }
1590
1591 m_infoLock->lock();
1592 QNetworkCacheMetaData urlData = m_manager->cache()->metaData(cacheUrl);
1593 m_infoLock->unlock();
1594
1595 if (urlData.isValid() &&
1596 ((!urlData.expirationDate().isValid()) ||
1597 (urlData.expirationDate().secsTo(now) < 0)))
1598 {
1599 if (urlData.lastModified().toUTC().secsTo(now) <= 3600) // 1 Hour
1600 {
1601 result = urlData.lastModified().toUTC();
1602 }
1603 else
1604 {
1605 QString date = getHeader(urlData, "Date");
1606 if (!date.isNull())
1607 {
1608 QDateTime loadDate =
1609 MythDate::fromString(date, kDateFormat);
1610#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1611 loadDate.setTimeSpec(Qt::UTC);
1612#else
1613 loadDate.setTimeZone(QTimeZone(QTimeZone::UTC));
1614#endif
1615 if (loadDate.secsTo(now) <= 1200) // 20 Minutes
1616 {
1617 result = urlData.lastModified().toUTC();
1618 }
1619 }
1620 }
1621 }
1622
1623 if (!result.isValid())
1624 {
1625 auto *dlInfo = new MythDownloadInfo;
1626 dlInfo->m_url = url;
1627 dlInfo->m_syncMode = true;
1628 // Head request, we only want to inspect the headers
1629 dlInfo->m_requestType = kRequestHead;
1630
1631 if (downloadNow(dlInfo, false))
1632 {
1633 if (dlInfo->m_reply)
1634 {
1635 QVariant lastMod =
1636 dlInfo->m_reply->header(
1637 QNetworkRequest::LastModifiedHeader);
1638 if (lastMod.isValid())
1639 result = lastMod.toDateTime().toUTC();
1640 }
1641
1642 // downloadNow() will set a flag to trigger downloadFinished()
1643 // to delete the dlInfo if the download times out
1644 delete dlInfo;
1645 }
1646 }
1647
1648 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1'): Result %2")
1649 .arg(url, result.toString()));
1650
1651 return result;
1652}
1653
1654
1659{
1660 QMutexLocker locker(&m_cookieLock);
1661
1662 auto *jar = new MythCookieJar;
1663 jar->load(filename);
1664 m_manager->setCookieJar(jar);
1665}
1666
1671{
1672 QMutexLocker locker(&m_cookieLock);
1673
1674 if (!m_manager->cookieJar())
1675 return;
1676
1677 auto *jar = qobject_cast<MythCookieJar *>(m_manager->cookieJar());
1678 if (jar == nullptr)
1679 return;
1680 jar->save(filename);
1681}
1682
1683void MythDownloadManager::setCookieJar(QNetworkCookieJar *cookieJar)
1684{
1685 QMutexLocker locker(&m_cookieLock);
1686 m_manager->setCookieJar(cookieJar);
1687}
1688
1693{
1694 QMutexLocker locker(&m_cookieLock);
1695
1696 if (!m_manager->cookieJar())
1697 return nullptr;
1698
1699 auto *inJar = qobject_cast<MythCookieJar *>(m_manager->cookieJar());
1700 if (inJar == nullptr)
1701 return nullptr;
1702 auto *outJar = new MythCookieJar;
1703 outJar->copyAllCookies(*inJar);
1704
1705 return outJar;
1706}
1707
1711void MythDownloadManager::refreshCookieJar(QNetworkCookieJar *jar)
1712{
1713 QMutexLocker locker(&m_cookieLock);
1714 delete m_inCookieJar;
1715
1716 auto *inJar = qobject_cast<MythCookieJar *>(jar);
1717 if (inJar == nullptr)
1718 return;
1719
1720 auto *outJar = new MythCookieJar;
1721 outJar->copyAllCookies(*inJar);
1722 m_inCookieJar = outJar;
1723
1724 QMutexLocker locker2(&m_queueWaitLock);
1725 m_queueWaitCond.wakeAll();
1726}
1727
1731{
1732 QMutexLocker locker(&m_cookieLock);
1733
1734 auto *inJar = qobject_cast<MythCookieJar *>(m_inCookieJar);
1735 if (inJar != nullptr)
1736 {
1737 auto *outJar = new MythCookieJar;
1738 outJar->copyAllCookies(*inJar);
1739 m_manager->setCookieJar(outJar);
1740 }
1741
1742 delete m_inCookieJar;
1743 m_inCookieJar = nullptr;
1744}
1745
1746QString MythDownloadManager::getHeader(const QUrl& url, const QString& header)
1747{
1748 if (!m_manager || !m_manager->cache())
1749 return {};
1750
1751 m_infoLock->lock();
1752 QNetworkCacheMetaData metadata = m_manager->cache()->metaData(url);
1753 m_infoLock->unlock();
1754
1755 return getHeader(metadata, header);
1756}
1757
1763QString MythDownloadManager::getHeader(const QNetworkCacheMetaData &cacheData,
1764 const QString& header)
1765{
1766 auto headers = cacheData.rawHeaders();
1767 for (const auto& rh : std::as_const(headers))
1768 if (QString(rh.first) == header)
1769 return {rh.second};
1770 return {};
1771}
1772
1773
1778{
1779 const QList<QNetworkCookie> cookieList = old.allCookies();
1780 setAllCookies(cookieList);
1781}
1782
1786void MythCookieJar::load(const QString &filename)
1787{
1788 LOG(VB_GENERAL, LOG_DEBUG, QString("MythCookieJar: loading cookies from: %1").arg(filename));
1789
1790 QFile f(filename);
1791 if (!f.open(QIODevice::ReadOnly))
1792 {
1793 LOG(VB_GENERAL, LOG_WARNING, QString("MythCookieJar::load() failed to open file for reading: %1").arg(filename));
1794 return;
1795 }
1796
1797 QList<QNetworkCookie> cookieList;
1798 QTextStream stream(&f);
1799 while (!stream.atEnd())
1800 {
1801 QString cookie = stream.readLine();
1802 cookieList << QNetworkCookie::parseCookies(cookie.toLocal8Bit());
1803 }
1804
1805 setAllCookies(cookieList);
1806}
1807
1811void MythCookieJar::save(const QString &filename)
1812{
1813 LOG(VB_GENERAL, LOG_DEBUG, QString("MythCookieJar: saving cookies to: %1").arg(filename));
1814
1815 QFile f(filename);
1816 if (!f.open(QIODevice::WriteOnly))
1817 {
1818 LOG(VB_GENERAL, LOG_ERR, QString("MythCookieJar::save() failed to open file for writing: %1").arg(filename));
1819 return;
1820 }
1821
1822 QList<QNetworkCookie> cookieList = allCookies();
1823 QTextStream stream(&f);
1824
1825 for (const auto& cookie : std::as_const(cookieList))
1826 stream << cookie.toRawForm() << Qt::endl;
1827}
1828
1829#include "moc_mythdownloadmanager.cpp"
static MThreadPool * globalInstance(void)
void start(QRunnable *runnable, const QString &debugName, int priority=0)
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:180
void RunEpilog(void)
Cleans up a thread's resources, call this if you reimplement run().
Definition: mthread.cpp:193
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:284
A subclassed QNetworkCookieJar that allows for reading and writing cookie files that contain raw form...
void load(const QString &filename)
Loads the cookie jar from a cookie file.
void save(const QString &filename)
Saves the cookie jar to a cookie file.
void copyAllCookies(MythCookieJar &old)
Copies all cookies from one MythCookieJar to another.
QString GetHostName(void)
const QHash< QByteArray, QByteArray > * m_headers
QNetworkReply::NetworkError m_errorCode
AuthCallback m_authCallback
MRequestType m_requestType
void SetDone(bool done)
QNetworkRequest * m_request
QNetworkReply * m_reply
QNetworkCookieJar * copyCookieJar(void)
Copy from one cookie jar to another.
void preCache(const QString &url)
Downloads a URL but doesn't store the resulting data anywhere.
QMap< QString, MythDownloadInfo * > m_downloadInfos
void downloadRemoteFile(MythDownloadInfo *dlInfo)
Triggers a myth:// URI download in the background via RemoteFile.
void queueItem(const QString &url, QNetworkRequest *req, const QString &dest, QByteArray *data, QObject *caller, MRequestType reqType=kRequestGet, bool reload=false)
Adds a request to the download queue.
~MythDownloadManager() override
Destructor for MythDownloadManager.
bool postAuth(const QString &url, QByteArray *data, AuthCallback authCallback, void *authArg, const QHash< QByteArray, QByteArray > *headers=nullptr)
Posts data to a url via the QNetworkAccessManager.
QNetworkCookieJar * m_inCookieJar
QNetworkDiskCache * m_diskCache
static bool saveFile(const QString &outFile, const QByteArray &data, bool append=false)
Saves a QByteArray of data to a given filename.
QWaitCondition m_queueWaitCond
bool downloadNow(MythDownloadInfo *dlInfo, bool deleteInfo=true)
Download helper for download() blocking methods.
QString getHeader(const QUrl &url, const QString &header)
void downloadQNetworkRequest(MythDownloadInfo *dlInfo)
Downloads a QNetworkRequest via the QNetworkAccessManager.
void queuePost(const QString &url, QByteArray *data, QObject *caller)
Queues a post to a URL via the QNetworkAccessManager.
void loadCookieJar(const QString &filename)
Loads the cookie jar from a cookie file.
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
Slot to process download update events.
void updateCookieJar(void)
Update the cookie jar from the temporary cookie jar.
bool post(const QString &url, QByteArray *data)
Posts data to a url via the QNetworkAccessManager.
bool processItem(const QString &url, QNetworkRequest *req, const QString &dest, QByteArray *data, MRequestType reqType=kRequestGet, bool reload=false, AuthCallback authCallback=nullptr, void *authArg=nullptr, const QHash< QByteArray, QByteArray > *headers=nullptr, QString *finalUrl=nullptr)
Processes a network request immediately and waits for a response.
QList< MythDownloadInfo * > m_cancellationQueue
QRecursiveMutex * m_infoLock
static QUrl redirectUrl(const QUrl &possibleRedirectUrl, const QUrl &oldRedirectUrl)
Checks whether we were redirected to the given URL.
friend class RemoteFileDownloadThread
QMap< QNetworkReply *, MythDownloadInfo * > m_downloadReplies
void authCallback(QNetworkReply *reply, QAuthenticator *authenticator)
Signal handler for authentication requests.
QList< MythDownloadInfo * > m_downloadQueue
void saveCookieJar(const QString &filename)
Saves the cookie jar to a cookie file.
bool downloadAuth(const QString &url, const QString &dest, bool reload=false, AuthCallback authCallback=nullptr, void *authArg=nullptr, const QHash< QByteArray, QByteArray > *headers=nullptr)
Downloads a URL to a file in blocking mode.
QNetworkAccessManager * m_manager
void setCookieJar(QNetworkCookieJar *cookieJar)
void run(void) override
Runs a loop to process incoming download requests and triggers download events to be processed.
void cancelDownload(const QString &url, bool block=true)
Cancel a queued or current download.
void removeListener(QObject *caller)
Disconnects the specified caller from any existing MythDownloadInfo instances.
void refreshCookieJar(QNetworkCookieJar *jar)
Refresh the temporary cookie jar from another cookie jar.
void queueDownload(const QString &url, const QString &dest, QObject *caller, bool reload=false)
Adds a url to the download queue.
QDateTime GetLastModified(const QString &url)
Gets the Last Modified timestamp for a URI.
void downloadError(QNetworkReply::NetworkError errorCode)
Slot to process download error events.
void downloadFinished(QNetworkReply *reply)
Slot to process download finished events.
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
This class is used as a container for messages.
Definition: mythevent.h:17
Small class to handle TCP port checking and finding link-local context.
Definition: portchecker.h:45
bool resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Convenience method to resolve link-local address.
MythDownloadManager * m_parent
RemoteFileDownloadThread(MythDownloadManager *parent, MythDownloadInfo *dlInfo)
unsigned int uint
Definition: compat.h:60
unsigned short uint16_t
Definition: iso6937tables.h:3
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
QString GetConfDir(void)
Definition: mythdirs.cpp:282
#define LOC
QMutex dmCreateLock
static constexpr int CACHE_REDIRECTION_LIMIT
void ShutdownMythDownloadManager(void)
Deletes the running MythDownloadManager at program exit.
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
MythDownloadManager * downloadManager
MRequestType
@ kRequestPost
@ kRequestHead
@ kRequestGet
void(*)(QNetworkReply *, QAuthenticator *, void *) AuthCallback
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
std::chrono::seconds secsInPast(const QDateTime &past)
Definition: mythdate.cpp:212
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15