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