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 <QNetworkCookieJar>
9 #include <QNetworkCookie>
10 #include <QAuthenticator>
11 #include <QTextStream>
12 #include <QNetworkProxy>
13 #include <QMutexLocker>
14 #include <QUrl>
15 #include <QTcpSocket>
16 
17 #include "stdlib.h"
18 
19 #include <unistd.h> // for usleep()
20 
21 // libmythbase
22 #include "compat.h"
23 #include "mythcorecontext.h"
24 #include "mythcoreutil.h"
25 #include "mthreadpool.h"
26 #include "mythdirs.h"
27 #include "mythevent.h"
28 #include "mythversion.h"
29 #include "remotefile.h"
30 #include "mythdate.h"
31 
32 #include "mythdownloadmanager.h"
33 #include "mythlogging.h"
34 #include "portchecker.h"
35 
36 using namespace std;
37 
38 #define LOC QString("DownloadManager: ")
39 #define CACHE_REDIRECTION_LIMIT 10
40 
42 QMutex dmCreateLock;
43 
48 {
49  public:
51  m_request(NULL), m_reply(NULL), m_data(NULL),
52  m_caller(NULL), m_requestType(kRequestGet),
53  m_reload(false), m_preferCache(false), m_syncMode(false),
54  m_processReply(true), m_done(false), m_bytesReceived(0),
55  m_bytesTotal(0), m_lastStat(MythDate::current()),
56  m_authCallback(NULL), m_authArg(NULL),
57  m_headers(NULL), m_errorCode(QNetworkReply::NoError)
58  {
59  qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
60  }
61 
63  {
64  if (m_request)
65  delete m_request;
66  if (m_reply && m_processReply)
67  m_reply->deleteLater();
68  }
69 
70  void detach(void)
71  {
72  m_url.detach();
73  m_outFile.detach();
74  }
75 
76  bool IsDone(void)
77  {
78  QMutexLocker lock(&m_lock);
79  return m_done;
80  }
81 
82  void SetDone(bool done)
83  {
84  QMutexLocker lock(&m_lock);
85  m_done = done;
86  }
87 
88  QString m_url;
90  QNetworkRequest *m_request;
91  QNetworkReply *m_reply;
92  QString m_outFile;
93  QByteArray *m_data;
94  QByteArray m_privData;
95  QObject *m_caller;
97  bool m_reload;
99  bool m_syncMode;
101  bool m_done;
103  qint64 m_bytesTotal;
104  QDateTime m_lastStat;
106  void *m_authArg;
107  const QHash<QByteArray, QByteArray> *m_headers;
108 
109  QNetworkReply::NetworkError m_errorCode;
110  QMutex m_lock;
111 };
112 
113 
118 class MythCookieJar : public QNetworkCookieJar
119 {
120  public:
121  MythCookieJar();
123  void load(const QString &filename);
124  void save(const QString &filename);
125 };
126 
130 class RemoteFileDownloadThread : public QRunnable
131 {
132  public:
134  MythDownloadInfo *dlInfo) :
135  m_parent(parent),
136  m_dlInfo(dlInfo)
137  {
138  m_dlInfo->detach();
139  }
140 
141  void run()
142  {
143  bool ok = false;
144 
145  RemoteFile *rf = new RemoteFile(m_dlInfo->m_url, false, false, 0);
146  ok = rf->SaveAs(m_dlInfo->m_privData);
147  delete rf;
148 
149  if (!ok)
150  m_dlInfo->m_errorCode = QNetworkReply::UnknownNetworkError;
151 
152  m_dlInfo->m_bytesReceived = m_dlInfo->m_privData.size();
153  m_dlInfo->m_bytesTotal = m_dlInfo->m_bytesReceived;
154 
155  m_parent->downloadFinished(m_dlInfo);
156  }
157 
158  private:
161 };
162 
166 {
167  if (downloadManager)
168  {
169  delete downloadManager;
170  downloadManager = NULL;
171  }
172 }
173 
178 {
179  if (downloadManager)
180  return downloadManager;
181 
182  QMutexLocker locker(&dmCreateLock);
183 
184  // Check once more in case the download manager was created
185  // while we were securing the lock.
186  if (downloadManager)
187  return downloadManager;
188 
190  tmpDLM->start();
191  while (!tmpDLM->getQueueThread())
192  usleep(10000);
193 
194  tmpDLM->moveToThread(tmpDLM->getQueueThread());
195  tmpDLM->setRunThread();
196 
197  while (!tmpDLM->isRunning())
198  usleep(10000);
199 
200  downloadManager = tmpDLM;
201 
203 
204  return downloadManager;
205 }
206 
211  MThread("DownloadManager"),
212  m_manager(NULL),
213  m_diskCache(NULL),
214  m_proxy(NULL),
215  m_infoLock(new QMutex(QMutex::Recursive)),
216  m_queueThread(NULL),
217  m_runThread(false),
218  m_isRunning(false),
219  m_inCookieJar(NULL)
220 {
221 }
222 
226 {
227  m_runThread = false;
228  m_queueWaitCond.wakeAll();
229 
230  wait();
231 
232  delete m_infoLock;
233 
234  if (m_inCookieJar)
235  delete m_inCookieJar;
236 }
237 
242 {
243  RunProlog();
244 
245  bool downloading = false;
246  bool itemsInQueue = false;
247  bool itemsInCancellationQueue = false;
248  bool waitAnyway = false;
249 
250  m_queueThread = QThread::currentThread();
251 
252  while (!m_runThread)
253  usleep(50000);
254 
255  m_manager = new QNetworkAccessManager(this);
256  m_diskCache = new QNetworkDiskCache(this);
257  m_proxy = new QNetworkProxy();
258  m_diskCache->setCacheDirectory(GetConfDir() + "/cache/" +
259  QCoreApplication::applicationName() + "-" +
261  m_manager->setCache(m_diskCache);
262 
263  // Set the proxy for the manager to be the application default proxy,
264  // which has already been setup
265  m_manager->setProxy(*m_proxy);
266 
267  // make sure the cookieJar is created in the same thread as the manager
268  // and set its parent to NULL so it can be shared between managers
269  m_manager->cookieJar()->setParent(NULL);
270 
271  QObject::connect(m_manager, SIGNAL(finished(QNetworkReply*)), this,
272  SLOT(downloadFinished(QNetworkReply*)));
273 
274  m_isRunning = true;
275  while (m_runThread)
276  {
277  if (m_inCookieJar)
278  {
279  LOG(VB_GENERAL, LOG_DEBUG, "Updating DLManager's Cookie Jar");
280  updateCookieJar();
281  }
282  m_infoLock->lock();
283  LOG(VB_FILE, LOG_DEBUG, LOC + QString("items downloading %1").arg(m_downloadInfos.count()));
284  LOG(VB_FILE, LOG_DEBUG, LOC + QString("items queued %1").arg(m_downloadQueue.count()));
285  downloading = !m_downloadInfos.isEmpty();
286  itemsInCancellationQueue = !m_cancellationQueue.isEmpty();
287  m_infoLock->unlock();
288 
289  if (itemsInCancellationQueue)
290  {
292  }
293  if (downloading)
294  QCoreApplication::processEvents();
295 
296  m_infoLock->lock();
297  itemsInQueue = !m_downloadQueue.isEmpty();
298  m_infoLock->unlock();
299 
300  if (!itemsInQueue || waitAnyway)
301  {
302  waitAnyway = false;
303  m_queueWaitLock.lock();
304 
305  if (downloading)
306  {
307  LOG(VB_FILE, LOG_DEBUG, LOC + QString("waiting 200ms"));
308  m_queueWaitCond.wait(&m_queueWaitLock, 200);
309  }
310  else
311  {
312  LOG(VB_FILE, LOG_DEBUG, LOC + QString("waiting for more items to download"));
314  }
315 
316  m_queueWaitLock.unlock();
317  }
318 
319  m_infoLock->lock();
320  if (!m_downloadQueue.isEmpty())
321  {
322  MythDownloadInfo *dlInfo = m_downloadQueue.front();
323 
324  m_downloadQueue.pop_front();
325 
326  if (!dlInfo)
327  {
328  m_infoLock->unlock();
329  continue;
330  }
331 
332  if (m_downloadInfos.contains(dlInfo->m_url))
333  {
334  // Push request to the end of the queue to let others process.
335  // If this is the only item in the queue, force the loop to
336  // wait a little.
337  if (m_downloadQueue.isEmpty())
338  waitAnyway = true;
339  m_downloadQueue.push_back(dlInfo);
340  m_infoLock->unlock();
341  continue;
342  }
343 
344  if (dlInfo->m_url.startsWith("myth://"))
345  downloadRemoteFile(dlInfo);
346  else
347  {
348  QMutexLocker cLock(&m_cookieLock);
349  downloadQNetworkRequest(dlInfo);
350  }
351 
352  m_downloadInfos[dlInfo->m_url] = dlInfo;
353  }
354  m_infoLock->unlock();
355  }
356  m_isRunning = false;
357 
358  RunEpilog();
359 }
360 
371 void MythDownloadManager::queueItem(const QString &url, QNetworkRequest *req,
372  const QString &dest, QByteArray *data,
373  QObject *caller, const MRequestType reqType,
374  const bool reload)
375 {
376  MythDownloadInfo *dlInfo = new MythDownloadInfo;
377 
378  dlInfo->m_url = url;
379  dlInfo->m_request = req;
380  dlInfo->m_outFile = dest;
381  dlInfo->m_data = data;
382  dlInfo->m_caller = caller;
383  dlInfo->m_requestType = reqType;
384  dlInfo->m_reload = reload;
385 
386  dlInfo->detach();
387 
388  QMutexLocker locker(m_infoLock);
389  m_downloadQueue.push_back(dlInfo);
390  m_queueWaitCond.wakeAll();
391 }
392 
405 bool MythDownloadManager::processItem(const QString &url, QNetworkRequest *req,
406  const QString &dest, QByteArray *data,
407  const MRequestType reqType,
408  const bool reload,
409  AuthCallback authCallback, void *authArg,
410  const QHash<QByteArray, QByteArray> *headers)
411 {
412  MythDownloadInfo *dlInfo = new MythDownloadInfo;
413 
414  dlInfo->m_url = url;
415  dlInfo->m_request = req;
416  dlInfo->m_outFile = dest;
417  dlInfo->m_data = data;
418  dlInfo->m_requestType = reqType;
419  dlInfo->m_reload = reload;
420  dlInfo->m_syncMode = true;
421  dlInfo->m_authCallback = authCallback;
422  dlInfo->m_authArg = authArg;
423  dlInfo->m_headers = headers;
424 
425  return downloadNow(dlInfo, true);
426 }
427 
431 void MythDownloadManager::preCache(const QString &url)
432 {
433  LOG(VB_FILE, LOG_DEBUG, LOC + QString("preCache('%1')").arg(url));
434  queueItem(url, NULL, QString(), NULL, NULL);
435 }
436 
443 void MythDownloadManager::queueDownload(const QString &url,
444  const QString &dest,
445  QObject *caller,
446  const bool reload)
447 {
448  LOG(VB_FILE, LOG_DEBUG, LOC + QString("queueDownload('%1', '%2', %3)")
449  .arg(url).arg(dest).arg((long long)caller));
450 
451  queueItem(url, NULL, dest, NULL, caller, kRequestGet, reload);
452 }
453 
459 void MythDownloadManager::queueDownload(QNetworkRequest *req,
460  QByteArray *data,
461  QObject *caller)
462 {
463  LOG(VB_FILE, LOG_DEBUG, LOC + QString("queueDownload('%1', '%2', %3)")
464  .arg(req->url().toString()).arg((long long)data)
465  .arg((long long)caller));
466 
467  queueItem(req->url().toString(), req, QString(), data, caller,
468  kRequestGet,
469  (QNetworkRequest::AlwaysNetwork == req->attribute(
470  QNetworkRequest::CacheLoadControlAttribute,
471  QNetworkRequest::PreferNetwork).toInt()));
472 }
473 
480 bool MythDownloadManager::download(const QString &url, const QString &dest,
481  const bool reload)
482 {
483  return processItem(url, NULL, dest, NULL, kRequestGet, reload);
484 }
485 
492 bool MythDownloadManager::download(const QString &url, QByteArray *data,
493  const bool reload)
494 {
495  return processItem(url, NULL, QString(), data, kRequestGet, reload);
496 }
497 
504 QNetworkReply *MythDownloadManager::download(const QString &url,
505  const bool reload)
506 {
507  MythDownloadInfo *dlInfo = new MythDownloadInfo;
508  QNetworkReply *reply = NULL;
509 
510  dlInfo->m_url = url;
511  dlInfo->m_reload = reload;
512  dlInfo->m_syncMode = true;
513  dlInfo->m_processReply = false;
514 
515  if (downloadNow(dlInfo, false))
516  {
517  if (dlInfo->m_reply)
518  {
519  reply = dlInfo->m_reply;
520  // prevent dlInfo dtor from deleting the reply
521  dlInfo->m_reply = NULL;
522 
523  delete dlInfo;
524 
525  return reply;
526  }
527 
528  delete dlInfo;
529  }
530 
531  return NULL;
532 }
533 
539 bool MythDownloadManager::download(QNetworkRequest *req, QByteArray *data)
540 {
541  LOG(VB_FILE, LOG_DEBUG, LOC + QString("download('%1', '%2')")
542  .arg(req->url().toString()).arg((long long)data));
543  return processItem(req->url().toString(), req, QString(), data,
544  kRequestGet,
545  (QNetworkRequest::AlwaysNetwork == req->attribute(
546  QNetworkRequest::CacheLoadControlAttribute,
547  QNetworkRequest::PreferNetwork).toInt()));
548 }
549 
559 bool MythDownloadManager::downloadAuth(const QString &url, const QString &dest,
560  const bool reload, AuthCallback authCallback, void *authArg,
561  const QHash<QByteArray, QByteArray> *headers)
562 {
563  return processItem(url, NULL, dest, NULL, kRequestGet, reload, authCallback,
564  authArg, headers);
565 }
566 
567 
573 void MythDownloadManager::queuePost(const QString &url,
574  QByteArray *data,
575  QObject *caller)
576 {
577  LOG(VB_FILE, LOG_DEBUG, LOC + QString("queuePost('%1', '%2')")
578  .arg(url).arg((long long)data));
579 
580  if (!data)
581  {
582  LOG(VB_GENERAL, LOG_ERR, LOC + "queuePost(), data is NULL!");
583  return;
584  }
585 
586  queueItem(url, NULL, QString(), data, caller, kRequestPost);
587 }
588 
594 void MythDownloadManager::queuePost(QNetworkRequest *req,
595  QByteArray *data,
596  QObject *caller)
597 {
598  LOG(VB_FILE, LOG_DEBUG, LOC + QString("queuePost('%1', '%2')")
599  .arg(req->url().toString()).arg((long long)data));
600 
601  if (!data)
602  {
603  LOG(VB_GENERAL, LOG_ERR, LOC + "queuePost(), data is NULL!");
604  return;
605  }
606 
607  queueItem(req->url().toString(), req, QString(), data, caller,
608  kRequestPost,
609  (QNetworkRequest::AlwaysNetwork == req->attribute(
610  QNetworkRequest::CacheLoadControlAttribute,
611  QNetworkRequest::PreferNetwork).toInt()));
612 
613 }
614 
620 bool MythDownloadManager::post(const QString &url, QByteArray *data)
621 {
622  LOG(VB_FILE, LOG_DEBUG, LOC + QString("post('%1', '%2')")
623  .arg(url).arg((long long)data));
624 
625  if (!data)
626  {
627  LOG(VB_GENERAL, LOG_ERR, LOC + "post(), data is NULL!");
628  return false;
629  }
630 
631  return processItem(url, NULL, QString(), data, kRequestPost);
632 }
633 
639 bool MythDownloadManager::post(QNetworkRequest *req, QByteArray *data)
640 {
641  LOG(VB_FILE, LOG_DEBUG, LOC + QString("post('%1', '%2')")
642  .arg(req->url().toString()).arg((long long)data));
643 
644  if (!data)
645  {
646  LOG(VB_GENERAL, LOG_ERR, LOC + "post(), data is NULL!");
647  return false;
648  }
649 
650  return processItem(req->url().toString(), req, QString(), data,
651  kRequestPost,
652  (QNetworkRequest::AlwaysNetwork == req->attribute(
653  QNetworkRequest::CacheLoadControlAttribute,
654  QNetworkRequest::PreferNetwork).toInt()));
655 
656 }
657 
666 bool MythDownloadManager::postAuth(const QString &url, QByteArray *data,
667  AuthCallback authCallback, void *authArg,
668  const QHash<QByteArray, QByteArray> *headers)
669 {
670  LOG(VB_FILE, LOG_DEBUG, LOC + QString("postAuth('%1', '%2')")
671  .arg(url).arg((long long)data));
672 
673  if (!data)
674  {
675  LOG(VB_GENERAL, LOG_ERR, LOC + "postAuth(), data is NULL!");
676  return false;
677  }
678 
679  return processItem(url, NULL, NULL, data, kRequestPost, false, authCallback,
680  authArg, headers);
681 }
682 
687 {
688  RemoteFileDownloadThread *dlThread =
689  new RemoteFileDownloadThread(this, dlInfo);
690  MThreadPool::globalInstance()->start(dlThread, "RemoteFileDownload");
691 }
692 
697 {
698  if (!dlInfo)
699  return;
700 
701  static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
702  QUrl qurl(dlInfo->m_url);
703  QNetworkRequest request;
704 
705  if (dlInfo->m_request)
706  {
707  request = *dlInfo->m_request;
708  delete dlInfo->m_request;
709  dlInfo->m_request = NULL;
710  }
711  else
712  request.setUrl(qurl);
713 
714  if (dlInfo->m_reload)
715  {
716  request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
717  QNetworkRequest::AlwaysNetwork);
718  }
719  else
720  {
721  // Prefer the in-cache item if one exists and it is less than 5 minutes
722  // old and it will not expire in the next 10 seconds
723  QDateTime now = MythDate::current();
724 
725  // Handle redirects, we want the metadata of the file headers
726  QString redirectLoc;
727  int limit = 0;
728  while (!(redirectLoc = getHeader(qurl, "Location")).isNull())
729  {
730  if (limit == CACHE_REDIRECTION_LIMIT)
731  {
732  LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
733  "reached for %1")
734  .arg(qurl.toString()));
735  return;
736  }
737  qurl.setUrl(redirectLoc);
738  limit++;
739  }
740 
741  LOG(VB_NETWORK, LOG_DEBUG, QString("Checking cache for %1")
742  .arg(qurl.toString()));
743 
744  m_infoLock->lock();
745  QNetworkCacheMetaData urlData = m_manager->cache()->metaData(qurl);
746  m_infoLock->unlock();
747  if ((urlData.isValid()) &&
748  ((!urlData.expirationDate().isValid()) ||
749  (QDateTime(urlData.expirationDate().toUTC()).secsTo(now) < 10)))
750  {
751  QString dateString = getHeader(urlData, "Date");
752 
753  if (!dateString.isNull())
754  {
755  QDateTime loadDate =
756  MythDate::fromString(dateString, dateFormat);
757  loadDate.setTimeSpec(Qt::UTC);
758  if (loadDate.secsTo(now) <= 720)
759  {
760  dlInfo->m_preferCache = true;
761  LOG(VB_NETWORK, LOG_DEBUG, QString("Preferring cache for %1")
762  .arg(qurl.toString()));
763  }
764  }
765  }
766  }
767 
768  if (dlInfo->m_preferCache)
769  request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
770  QNetworkRequest::PreferCache);
771 
772  if (!request.hasRawHeader("User-Agent"))
773  {
774  request.setRawHeader("User-Agent",
775  "MythTV v" MYTH_BINARY_VERSION
776  " MythDownloadManager");
777  }
778 
779  if (dlInfo->m_headers)
780  {
781  QHash<QByteArray, QByteArray>::const_iterator it =
782  dlInfo->m_headers->constBegin();
783  for ( ; it != dlInfo->m_headers->constEnd(); ++it )
784  {
785  if (!it.key().isEmpty() && !it.value().isEmpty())
786  {
787  request.setRawHeader(it.key(), it.value());
788  }
789  }
790  }
791 
792  switch (dlInfo->m_requestType)
793  {
794  case kRequestPost :
795  dlInfo->m_reply = m_manager->post(request, *dlInfo->m_data);
796  break;
797  case kRequestHead :
798  dlInfo->m_reply = m_manager->head(request);
799  break;
800  case kRequestGet :
801  default:
802  dlInfo->m_reply = m_manager->get(request);
803  break;
804  }
805 
806  m_downloadReplies[dlInfo->m_reply] = dlInfo;
807 
808  if (dlInfo->m_authCallback)
809  {
810  connect(m_manager, SIGNAL(authenticationRequired(QNetworkReply *,
811  QAuthenticator *)),
812  this, SLOT(authCallback(QNetworkReply *, QAuthenticator *)));
813  }
814 
815  connect(dlInfo->m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
816  SLOT(downloadError(QNetworkReply::NetworkError)));
817  connect(dlInfo->m_reply, SIGNAL(downloadProgress(qint64, qint64)),
818  this, SLOT(downloadProgress(qint64, qint64)));
819 }
820 
825 void MythDownloadManager::authCallback(QNetworkReply *reply,
826  QAuthenticator *authenticator)
827 {
828  if (!reply)
829  return;
830 
831  MythDownloadInfo *dlInfo = m_downloadReplies[reply];
832 
833  if (!dlInfo)
834  return;
835 
836  if (dlInfo->m_authCallback)
837  {
838  LOG(VB_FILE, LOG_DEBUG, "Calling auth callback");
839  dlInfo->m_authCallback(reply, authenticator, dlInfo->m_authArg);
840  }
841 }
842 
850 {
851  if (!dlInfo)
852  return false;
853 
854  dlInfo->m_syncMode = true;
855 
856  // Special handling for link-local
857  // Not needed for Windows because windows does not need
858  // the scope id.
859 #ifndef _WIN32
860  if (dlInfo->m_url.startsWith("http://[fe80::",Qt::CaseInsensitive))
861  return downloadNowLinkLocal(dlInfo,deleteInfo);
862 #endif
863  m_infoLock->lock();
864  m_downloadQueue.push_back(dlInfo);
865  m_infoLock->unlock();
866  m_queueWaitCond.wakeAll();
867 
868  // timeout myth:// RemoteFile transfers 20 seconds from now
869  // timeout non-myth:// QNetworkAccessManager transfers 60 seconds after
870  // their last progress update
871  QDateTime startedAt = MythDate::current();
872  m_infoLock->lock();
873  while ((!dlInfo->IsDone()) &&
874  (dlInfo->m_errorCode == QNetworkReply::NoError) &&
875  (((!dlInfo->m_url.startsWith("myth://")) &&
876  (dlInfo->m_lastStat.secsTo(MythDate::current()) < 60)) ||
877  ((dlInfo->m_url.startsWith("myth://")) &&
878  (startedAt.secsTo(MythDate::current()) < 20))))
879  {
880  m_infoLock->unlock();
881  m_queueWaitLock.lock();
882  m_queueWaitCond.wait(&m_queueWaitLock, 200);
883  m_queueWaitLock.unlock();
884  m_infoLock->lock();
885  }
886  bool done = dlInfo->IsDone();
887  bool success =
888  done && (dlInfo->m_errorCode == QNetworkReply::NoError);
889 
890  if (!done)
891  {
892  dlInfo->m_data = NULL; // Prevent downloadFinished() from updating
893  dlInfo->m_syncMode = false; // Let downloadFinished() cleanup for us
894  if ((dlInfo->m_reply) &&
895  (dlInfo->m_errorCode == QNetworkReply::NoError))
896  {
897  LOG(VB_FILE, LOG_DEBUG,
898  LOC + QString("Aborting download - lack of data transfer"));
899  dlInfo->m_reply->abort();
900  }
901  }
902  else if (deleteInfo)
903  delete dlInfo;
904 
905  m_infoLock->unlock();
906 
907  return success;
908 }
909 
910 #ifndef _WIN32
911 
931 bool MythDownloadManager::downloadNowLinkLocal(MythDownloadInfo *dlInfo, bool deleteInfo)
932 {
933  bool isOK = true;
934 
935  // Only certain features are supported here
936  if (dlInfo->m_authCallback || dlInfo->m_authArg)
937  {
938  LOG(VB_GENERAL, LOG_ERR, LOC +
939  QString("Unsupported authentication for %1").arg(dlInfo->m_url));
940  isOK = false;
941  }
942  if (!dlInfo->m_outFile.isEmpty())
943  {
944  LOG(VB_GENERAL, LOG_ERR, LOC +
945  QString("Unsupported File output %1 for %2")
946  .arg(dlInfo->m_outFile).arg(dlInfo->m_url));
947  isOK = false;
948  }
949 
950  if (!deleteInfo || dlInfo->m_requestType == kRequestHead)
951  {
952  // We do not have the ability to return a network reply in dlInfo
953  // so if we are asked to do that, return an error.
954  LOG(VB_GENERAL, LOG_ERR, LOC +
955  QString("Unsupported link-local operation %1")
956  .arg(dlInfo->m_url));
957  isOK = false;
958  }
959 
960  QUrl url(dlInfo->m_url);
961  QString host(url.host());
962  int port(url.port(80));
963  if (isOK && PortChecker::resolveLinkLocal(host, port))
964  {
965  QString reqType;
966  switch (dlInfo->m_requestType)
967  {
968  case kRequestPost :
969  reqType = "POST";
970  break;
971  case kRequestGet :
972  default:
973  reqType = "GET";
974  break;
975  }
976  QByteArray *aBuffer = dlInfo->m_data;
977  QHash<QByteArray, QByteArray> headers;
978  if (dlInfo->m_headers)
979  headers = *dlInfo->m_headers;
980  if (!headers.contains("User-Agent"))
981  headers.insert("User-Agent",
982  "MythDownloadManager v" MYTH_BINARY_VERSION);
983  headers.insert("Connection", "close");
984  headers.insert("Accept-Encoding", "identity");
985  if (aBuffer && !aBuffer->isEmpty())
986  headers.insert("Content-Length",
987  (QString::number(aBuffer->size())).toUtf8());
988  headers.insert("Host",
989  (url.host()+":"+QString::number(port)).toUtf8());
990 
991  QByteArray requestMessage;
992  QString path (url.path());
993  requestMessage.append("POST ");
994  requestMessage.append(path);
995  requestMessage.append(" HTTP/1.1\r\n");
996  QHashIterator<QByteArray, QByteArray> it(headers);
997  while (it.hasNext())
998  {
999  it.next();
1000  requestMessage.append(it.key());
1001  requestMessage.append(": ");
1002  requestMessage.append(it.value());
1003  requestMessage.append("\r\n");
1004  }
1005  requestMessage.append("\r\n");
1006  if (aBuffer && !aBuffer->isEmpty())
1007  {
1008  requestMessage.append(*aBuffer);
1009  }
1010  QTcpSocket socket;
1011  socket.connectToHost(host, port);
1012  // QT Warning - this may not work on Windows
1013  if (!socket.waitForConnected(5000))
1014  isOK = false;
1015  if (isOK)
1016  isOK = (socket.write(requestMessage) > 0);
1017  if (isOK)
1018  // QT Warning - this may not work on Windows
1019  isOK = socket.waitForDisconnected(5000);
1020  if (isOK)
1021  {
1022  *aBuffer = socket.readAll();
1023  // Find the start of the content
1024  QByteArray delim("\r\n\r\n");
1025  int delimLoc=aBuffer->indexOf(delim);
1026  if (delimLoc > -1)
1027  *aBuffer = aBuffer->right
1028  (aBuffer->size()-delimLoc-4);
1029  else
1030  isOK=false;
1031  }
1032  socket.close();
1033  }
1034  else
1035  isOK = false;
1036 
1037  if (deleteInfo)
1038  delete dlInfo;
1039 
1040  if (isOK)
1041  return true;
1042  else
1043  {
1044  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Link Local request failed: %1")
1045  .arg(url.toString()));
1046  return false;
1047  }
1048 
1049 }
1050 #endif
1051 
1056 void MythDownloadManager::cancelDownload(const QString &url, bool block)
1057 {
1058  cancelDownload(QStringList(url), block);
1059 }
1060 
1065 void MythDownloadManager::cancelDownload(const QStringList &urls, bool block)
1066 {
1067  MythDownloadInfo *dlInfo;
1068 
1069  m_infoLock->lock();
1070  foreach (QString url, urls)
1071  {
1072  QMutableListIterator<MythDownloadInfo*> lit(m_downloadQueue);
1073  while (lit.hasNext())
1074  {
1075  lit.next();
1076  dlInfo = lit.value();
1077  if (dlInfo->m_url == url)
1078  {
1079  if (!m_cancellationQueue.contains(dlInfo))
1080  m_cancellationQueue.append(dlInfo);
1081  lit.remove();
1082  }
1083  }
1084 
1085  if (m_downloadInfos.contains(url))
1086  {
1087  dlInfo = m_downloadInfos[url];
1088 
1089  if (!m_cancellationQueue.contains(dlInfo))
1090  m_cancellationQueue.append(dlInfo);
1091 
1092  if (dlInfo->m_reply)
1093  m_downloadReplies.remove(dlInfo->m_reply);
1094 
1095  m_downloadInfos.remove(url);
1096  }
1097  }
1098  m_infoLock->unlock();
1099 
1100  if (QThread::currentThread() == this->thread())
1101  {
1102  downloadCanceled();
1103  return;
1104  }
1105 
1106  // wake-up running thread
1107  m_queueWaitCond.wakeAll();
1108 
1109  if (!block)
1110  return;
1111 
1112  while (!m_cancellationQueue.isEmpty())
1113  {
1114  usleep(50000); // re-test in another 50ms
1115  }
1116 }
1117 
1119 {
1120  QMutexLocker locker(m_infoLock);
1121  MythDownloadInfo *dlInfo;
1122 
1123  QMutableListIterator<MythDownloadInfo*> lit(m_cancellationQueue);
1124  while (lit.hasNext())
1125  {
1126  lit.next();
1127  dlInfo = lit.value();
1128  dlInfo->m_lock.lock();
1129 
1130  if (dlInfo->m_reply)
1131  {
1132  LOG(VB_FILE, LOG_DEBUG,
1133  LOC + QString("Aborting download - user request"));
1134  dlInfo->m_reply->abort();
1135  }
1136  lit.remove();
1137  if (dlInfo->m_done)
1138  {
1139  dlInfo->m_lock.unlock();
1140  continue;
1141  }
1142  dlInfo->m_errorCode = QNetworkReply::OperationCanceledError;
1143  dlInfo->m_done = true;
1144  dlInfo->m_lock.unlock();
1145  }
1146 }
1147 
1153 {
1154  QMutexLocker locker(m_infoLock);
1155  MythDownloadInfo *dlInfo;
1156 
1157  QList <MythDownloadInfo*>::iterator lit = m_downloadQueue.begin();
1158  for (; lit != m_downloadQueue.end(); ++lit)
1159  {
1160  dlInfo = *lit;
1161  if (dlInfo->m_caller == caller)
1162  {
1163  dlInfo->m_caller = NULL;
1164  dlInfo->m_outFile = QString();
1165  dlInfo->m_data = NULL;
1166  }
1167  }
1168 
1169  QMap <QString, MythDownloadInfo*>::iterator mit = m_downloadInfos.begin();
1170  for (; mit != m_downloadInfos.end(); ++mit)
1171  {
1172  dlInfo = mit.value();
1173  if (dlInfo->m_caller == caller)
1174  {
1175  dlInfo->m_caller = NULL;
1176  dlInfo->m_outFile = QString();
1177  dlInfo->m_data = NULL;
1178  }
1179  }
1180 }
1181 
1185 void MythDownloadManager::downloadError(QNetworkReply::NetworkError errorCode)
1186 {
1187  QNetworkReply *reply = (QNetworkReply*)sender();
1188 
1189  LOG(VB_FILE, LOG_DEBUG, LOC + QString("downloadError %1 ")
1190  .arg(errorCode) + reply->errorString() );
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)
1202  return;
1203 
1204  dlInfo->m_errorCode = errorCode;
1205 }
1206 
1212 QUrl MythDownloadManager::redirectUrl(const QUrl& possibleRedirectUrl,
1213  const QUrl& oldRedirectUrl) const
1214 {
1215  LOG(VB_FILE, LOG_DEBUG, LOC + QString("redirectUrl()"));
1216  QUrl redirectUrl;
1217 
1218  if(!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != oldRedirectUrl)
1219  redirectUrl = possibleRedirectUrl;
1220 
1221  return redirectUrl;
1222 }
1223 
1227 void MythDownloadManager::downloadFinished(QNetworkReply* reply)
1228 {
1229  LOG(VB_FILE, LOG_DEBUG, LOC + QString("downloadFinished(%1)")
1230  .arg((long long)reply));
1231 
1232  QMutexLocker locker(m_infoLock);
1233  if (!m_downloadReplies.contains(reply))
1234  {
1235  reply->deleteLater();
1236  return;
1237  }
1238 
1239  MythDownloadInfo *dlInfo = m_downloadReplies[reply];
1240 
1241  if (!dlInfo || !dlInfo->m_reply)
1242  return;
1243 
1244  downloadFinished(dlInfo);
1245 }
1246 
1251 {
1252  if (!dlInfo)
1253  return;
1254 
1255  int statusCode = -1;
1256  static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
1257  QNetworkReply *reply = dlInfo->m_reply;
1258 
1259  if (reply)
1260  {
1261  QUrl possibleRedirectUrl =
1262  reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
1263 
1264  if (!possibleRedirectUrl.isEmpty() &&
1265  possibleRedirectUrl.isValid() &&
1266  possibleRedirectUrl.isRelative()) // Turn relative Url to absolute
1267  possibleRedirectUrl = reply->url().resolved(possibleRedirectUrl);
1268 
1269  dlInfo->m_redirectedTo =
1270  redirectUrl(possibleRedirectUrl, dlInfo->m_redirectedTo);
1271 
1272  QVariant status =
1273  reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
1274  if (status.isValid())
1275  statusCode = status.toInt();
1276  }
1277 
1278  if(reply && !dlInfo->m_redirectedTo.isEmpty() &&
1279  ((dlInfo->m_requestType != kRequestPost) ||
1280  (statusCode == 301 || statusCode == 302 ||
1281  statusCode == 303)))
1282  {
1283  LOG(VB_FILE, LOG_DEBUG, LOC +
1284  QString("downloadFinished(%1): Redirect: %2 -> %3")
1285  .arg((long long)dlInfo)
1286  .arg(reply->url().toString())
1287  .arg(dlInfo->m_redirectedTo.toString()));
1288 
1289  if (dlInfo->m_data)
1290  dlInfo->m_data->clear();
1291 
1292  dlInfo->m_bytesReceived = 0;
1293  dlInfo->m_bytesTotal = 0;
1294 
1295  QNetworkRequest request(dlInfo->m_redirectedTo);
1296 
1297  if (dlInfo->m_reload)
1298  {
1299  request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
1300  QNetworkRequest::AlwaysNetwork);
1301  }
1302  else if (dlInfo->m_preferCache)
1303  {
1304  request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
1305  QNetworkRequest::PreferCache);
1306  }
1307 
1308  request.setRawHeader("User-Agent",
1309  "MythDownloadManager v" MYTH_BINARY_VERSION);
1310 
1311  switch (dlInfo->m_requestType)
1312  {
1313  case kRequestHead :
1314  dlInfo->m_reply = m_manager->head(request);
1315  break;
1316  case kRequestGet :
1317  default:
1318  dlInfo->m_reply = m_manager->get(request);
1319  break;
1320  }
1321 
1322  m_downloadReplies[dlInfo->m_reply] = dlInfo;
1323 
1324  connect(dlInfo->m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
1325  this, SLOT(downloadError(QNetworkReply::NetworkError)));
1326  connect(dlInfo->m_reply, SIGNAL(downloadProgress(qint64, qint64)),
1327  this, SLOT(downloadProgress(qint64, qint64)));
1328 
1329  m_downloadReplies.remove(reply);
1330  reply->deleteLater();
1331  }
1332  else
1333  {
1334  LOG(VB_FILE, LOG_DEBUG, QString("downloadFinished(%1): COMPLETE: %2")
1335  .arg((long long)dlInfo).arg(dlInfo->m_url));
1336 
1337  // HACK Insert a Date header into the cached metadata if one doesn't
1338  // already exist
1339  QUrl fileUrl = dlInfo->m_url;
1340  QString redirectLoc;
1341  int limit = 0;
1342  while (!(redirectLoc = getHeader(fileUrl, "Location")).isNull())
1343  {
1344  if (limit == CACHE_REDIRECTION_LIMIT)
1345  {
1346  LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
1347  "reached for %1")
1348  .arg(fileUrl.toString()));
1349  return;
1350  }
1351  fileUrl.setUrl(redirectLoc);
1352  limit++;
1353  }
1354 
1355  m_infoLock->lock();
1356  QNetworkCacheMetaData urlData = m_manager->cache()->metaData(fileUrl);
1357  m_infoLock->unlock();
1358  if (getHeader(urlData, "Date").isNull())
1359  {
1360  QNetworkCacheMetaData::RawHeaderList headers = urlData.rawHeaders();
1361  QNetworkCacheMetaData::RawHeader newheader;
1362  QDateTime now = MythDate::current();
1363  newheader = QNetworkCacheMetaData::RawHeader("Date",
1364  now.toString(dateFormat).toLatin1());
1365  headers.append(newheader);
1366  urlData.setRawHeaders(headers);
1367  m_infoLock->lock();
1368  m_manager->cache()->updateMetaData(urlData);
1369  m_infoLock->unlock();
1370  }
1371  // End HACK
1372 
1373  dlInfo->m_redirectedTo.clear();
1374 
1375  int dataSize = -1;
1376 
1377  // If we downloaded via the QNetworkAccessManager
1378  // AND the caller isn't handling the reply directly
1379  if (reply && dlInfo->m_processReply)
1380  {
1381  bool append = (!dlInfo->m_syncMode && dlInfo->m_caller);
1382  QByteArray data = reply->readAll();
1383  dataSize = data.size();
1384 
1385  if (append)
1386  dlInfo->m_bytesReceived += dataSize;
1387  else
1388  dlInfo->m_bytesReceived = dataSize;
1389 
1390  dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
1391 
1392  if (dlInfo->m_data)
1393  {
1394  if (append)
1395  dlInfo->m_data->append(data);
1396  else
1397  *dlInfo->m_data = data;
1398  }
1399  else if (!dlInfo->m_outFile.isEmpty())
1400  {
1401  saveFile(dlInfo->m_outFile, data, append);
1402  }
1403  }
1404  else if (!reply) // If we downloaded via RemoteFile
1405  {
1406  if (dlInfo->m_data)
1407  {
1408  (*dlInfo->m_data) = dlInfo->m_privData;
1409  }
1410  else if (!dlInfo->m_outFile.isEmpty())
1411  {
1412  saveFile(dlInfo->m_outFile, dlInfo->m_privData);
1413  }
1414  dlInfo->m_bytesReceived += dataSize;
1415  dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
1416  }
1417  // else we downloaded via QNetworkAccessManager
1418  // AND the caller is handling the reply
1419 
1420  m_infoLock->lock();
1421  if (!m_downloadInfos.remove(dlInfo->m_url))
1422  LOG(VB_GENERAL, LOG_ERR, LOC +
1423  QString("ERROR download finished but failed to remove url: %1")
1424  .arg(dlInfo->m_url));
1425 
1426  if (reply)
1427  m_downloadReplies.remove(reply);
1428  m_infoLock->unlock();
1429 
1430  dlInfo->SetDone(true);
1431 
1432  if (!dlInfo->m_syncMode)
1433  {
1434  if (dlInfo->m_caller)
1435  {
1436  LOG(VB_FILE, LOG_DEBUG, QString("downloadFinished(%1): "
1437  "COMPLETE: %2, sending event to caller")
1438  .arg((long long)dlInfo).arg(dlInfo->m_url));
1439 
1440  QStringList args;
1441  args << dlInfo->m_url;
1442  args << dlInfo->m_outFile;
1443  args << QString::number(dlInfo->m_bytesTotal);
1444  // placeholder for error string
1445  args << (reply ? reply->errorString() : QString());
1446  args << QString::number((int)(reply ? reply->error() :
1447  dlInfo->m_errorCode));
1448 
1449  QCoreApplication::postEvent(dlInfo->m_caller,
1450  new MythEvent("DOWNLOAD_FILE FINISHED", args));
1451  }
1452 
1453  delete dlInfo;
1454  }
1455 
1456  m_queueWaitCond.wakeAll();
1457  }
1458 }
1459 
1465 void MythDownloadManager::downloadProgress(qint64 bytesReceived,
1466  qint64 bytesTotal)
1467 {
1468  QNetworkReply *reply = (QNetworkReply*)sender();
1469 
1470  LOG(VB_FILE, LOG_DEBUG, LOC +
1471  QString("downloadProgress(%1, %2) (for reply %3)")
1472  .arg(bytesReceived).arg(bytesTotal).arg((long long)reply));
1473 
1474  QMutexLocker locker(m_infoLock);
1475  if (!m_downloadReplies.contains(reply))
1476  return;
1477 
1478  MythDownloadInfo *dlInfo = m_downloadReplies[reply];
1479 
1480  if (!dlInfo)
1481  return;
1482 
1483  dlInfo->m_lastStat = MythDate::current();
1484 
1485  LOG(VB_FILE, LOG_DEBUG, LOC +
1486  QString("downloadProgress: %1 to %2 is at %3 of %4 bytes downloaded")
1487  .arg(dlInfo->m_url).arg(dlInfo->m_outFile)
1488  .arg(bytesReceived).arg(bytesTotal));
1489 
1490  if (!dlInfo->m_syncMode && dlInfo->m_caller)
1491  {
1492  LOG(VB_FILE, LOG_DEBUG, QString("downloadProgress(%1): "
1493  "sending event to caller")
1494  .arg(reply->url().toString()));
1495 
1496  bool appendToFile = (dlInfo->m_bytesReceived != 0);
1497  QByteArray data = reply->readAll();
1498  if (!dlInfo->m_outFile.isEmpty())
1499  saveFile(dlInfo->m_outFile, data, appendToFile);
1500 
1501  if (dlInfo->m_data)
1502  dlInfo->m_data->append(data);
1503 
1504  dlInfo->m_bytesReceived = bytesReceived;
1505  dlInfo->m_bytesTotal = bytesTotal;
1506 
1507  QStringList args;
1508  args << dlInfo->m_url;
1509  args << dlInfo->m_outFile;
1510  args << QString::number(bytesReceived);
1511  args << QString::number(bytesTotal);
1512 
1513  QCoreApplication::postEvent(dlInfo->m_caller,
1514  new MythEvent("DOWNLOAD_FILE UPDATE", args));
1515  }
1516 }
1517 
1525 bool MythDownloadManager::saveFile(const QString &outFile,
1526  const QByteArray &data,
1527  const bool append)
1528 {
1529  if (outFile.isEmpty() || !data.size())
1530  return false;
1531 
1532  QFile file(outFile);
1533  QFileInfo fileInfo(outFile);
1534  QDir qdir(fileInfo.absolutePath());
1535 
1536  if (!qdir.exists() && !qdir.mkpath(fileInfo.absolutePath()))
1537  {
1538  LOG(VB_GENERAL, LOG_ERR, QString("Failed to create: '%1'")
1539  .arg(fileInfo.absolutePath()));
1540  return false;
1541  }
1542 
1543  QIODevice::OpenMode mode = QIODevice::Unbuffered|QIODevice::WriteOnly;
1544  if (append)
1545  mode |= QIODevice::Append;
1546 
1547  if (!file.open(mode))
1548  {
1549  LOG(VB_GENERAL, LOG_ERR, QString("Failed to open: '%1'") .arg(outFile));
1550  return false;
1551  }
1552 
1553  off_t offset = 0;
1554  size_t remaining = data.size();
1555  uint failure_cnt = 0;
1556  while ((remaining > 0) && (failure_cnt < 5))
1557  {
1558  ssize_t written = file.write(data.data() + offset, remaining);
1559  if (written < 0)
1560  {
1561  failure_cnt++;
1562  usleep(50000);
1563  continue;
1564  }
1565 
1566  failure_cnt = 0;
1567  offset += written;
1568  remaining -= written;
1569  }
1570 
1571  if (remaining > 0)
1572  return false;
1573 
1574  return true;
1575 }
1576 
1581 QDateTime MythDownloadManager::GetLastModified(const QString &url)
1582 {
1583  // If the header has not expired and
1584  // the last modification date is less than 1 hours old or if
1585  // the cache object is less than 20 minutes old,
1586  // then use the cached header otherwise redownload the header
1587 
1588  static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
1589  LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1')").arg(url));
1590  QDateTime result;
1591 
1592  QDateTime now = MythDate::current();
1593 
1594  QUrl cacheUrl = QUrl(url);
1595 
1596  // Deal with redirects, we want the cached data for the final url
1597  QString redirectLoc;
1598  int limit = 0;
1599  while (!(redirectLoc = getHeader(cacheUrl, "Location")).isNull())
1600  {
1601  if (limit == CACHE_REDIRECTION_LIMIT)
1602  {
1603  LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
1604  "reached for %1")
1605  .arg(cacheUrl.toString()));
1606  return result;
1607  }
1608  cacheUrl.setUrl(redirectLoc);
1609  limit++;
1610  }
1611 
1612  m_infoLock->lock();
1613  QNetworkCacheMetaData urlData = m_manager->cache()->metaData(cacheUrl);
1614  m_infoLock->unlock();
1615 
1616  if (urlData.isValid() &&
1617  ((!urlData.expirationDate().isValid()) ||
1618  (urlData.expirationDate().secsTo(now) < 0)))
1619  {
1620  if (QDateTime(urlData.lastModified().toUTC()).secsTo(now) <= 3600) // 1 Hour
1621  {
1622  result = urlData.lastModified().toUTC();
1623  }
1624  else
1625  {
1626  QString date = getHeader(urlData, "Date");
1627  if (!date.isNull())
1628  {
1629  QDateTime loadDate =
1630  MythDate::fromString(date, dateFormat);
1631  loadDate.setTimeSpec(Qt::UTC);
1632  if (loadDate.secsTo(now) <= 1200) // 20 Minutes
1633  {
1634  result = urlData.lastModified().toUTC();
1635  }
1636  }
1637  }
1638  }
1639 
1640  if (!result.isValid())
1641  {
1642  MythDownloadInfo *dlInfo = new MythDownloadInfo;
1643  dlInfo->m_url = url;
1644  dlInfo->m_syncMode = true;
1645  // Head request, we only want to inspect the headers
1646  dlInfo->m_requestType = kRequestHead;
1647 
1648  if (downloadNow(dlInfo, false))
1649  {
1650  if (dlInfo->m_reply)
1651  {
1652  QVariant lastMod =
1653  dlInfo->m_reply->header(
1654  QNetworkRequest::LastModifiedHeader);
1655  if (lastMod.isValid())
1656  result = lastMod.toDateTime().toUTC();
1657  }
1658 
1659  // downloadNow() will set a flag to trigger downloadFinished()
1660  // to delete the dlInfo if the download times out
1661  delete dlInfo;
1662  }
1663  }
1664 
1665  LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1'): Result %2")
1666  .arg(url).arg(result.toString()));
1667 
1668  return result;
1669 }
1670 
1671 
1676 {
1677  QMutexLocker locker(&m_cookieLock);
1678 
1679  MythCookieJar *jar = new MythCookieJar;
1680  jar->load(filename);
1681  m_manager->setCookieJar(jar);
1682 }
1683 
1688 {
1689  QMutexLocker locker(&m_cookieLock);
1690 
1691  if (!m_manager->cookieJar())
1692  return;
1693 
1694  MythCookieJar *jar = static_cast<MythCookieJar *>(m_manager->cookieJar());
1695  jar->save(filename);
1696 }
1697 
1698 void MythDownloadManager::setCookieJar(QNetworkCookieJar *cookieJar)
1699 {
1700  QMutexLocker locker(&m_cookieLock);
1701  m_manager->setCookieJar(cookieJar);
1702 }
1703 
1707 QNetworkCookieJar *MythDownloadManager::copyCookieJar(void)
1708 {
1709  QMutexLocker locker(&m_cookieLock);
1710 
1711  if (!m_manager->cookieJar())
1712  return NULL;
1713 
1714  MythCookieJar *inJar = static_cast<MythCookieJar *>(m_manager->cookieJar());
1715  MythCookieJar *outJar = new MythCookieJar(*inJar);
1716 
1717  return static_cast<QNetworkCookieJar *>(outJar);
1718 }
1719 
1723 void MythDownloadManager::refreshCookieJar(QNetworkCookieJar *jar)
1724 {
1725  QMutexLocker locker(&m_cookieLock);
1726  if (m_inCookieJar)
1727  delete m_inCookieJar;
1728 
1729  MythCookieJar *inJar = static_cast<MythCookieJar *>(jar);
1730  MythCookieJar *outJar = new MythCookieJar(*inJar);
1731  m_inCookieJar = static_cast<QNetworkCookieJar *>(outJar);
1732 
1733  QMutexLocker locker2(&m_queueWaitLock);
1734  m_queueWaitCond.wakeAll();
1735 }
1736 
1740 {
1741  QMutexLocker locker(&m_cookieLock);
1742 
1743  MythCookieJar *inJar = static_cast<MythCookieJar *>(m_inCookieJar);
1744  MythCookieJar *outJar = new MythCookieJar(*inJar);
1745  m_manager->setCookieJar(static_cast<QNetworkCookieJar *>(outJar));
1746 
1747  delete m_inCookieJar;
1748  m_inCookieJar = NULL;
1749 }
1750 
1751 QString MythDownloadManager::getHeader(const QUrl& url, const QString& header)
1752 {
1753  if (!m_manager || !m_manager->cache())
1754  return QString::null;
1755 
1756  m_infoLock->lock();
1757  QNetworkCacheMetaData metadata = m_manager->cache()->metaData(url);
1758  m_infoLock->unlock();
1759 
1760  return getHeader(metadata, header);
1761 }
1762 
1768 QString MythDownloadManager::getHeader(const QNetworkCacheMetaData &cacheData,
1769  const QString& header)
1770 {
1771  QNetworkCacheMetaData::RawHeaderList headers = cacheData.rawHeaders();
1772  bool found = false;
1773  QNetworkCacheMetaData::RawHeaderList::iterator it = headers.begin();
1774  for (; !found && it != headers.end(); ++it)
1775  {
1776  if (QString((*it).first) == header)
1777  {
1778  found = true;
1779  return QString((*it).second);
1780  }
1781  }
1782 
1783  return QString::null;
1784 }
1785 
1786 
1791 {
1792  const QList<QNetworkCookie> cookieList = old.allCookies();
1793  setAllCookies(cookieList);
1794 }
1795 
1799 {
1800 }
1801 
1805 void MythCookieJar::load(const QString &filename)
1806 {
1807  LOG(VB_GENERAL, LOG_DEBUG, QString("MythCookieJar: loading cookies from: %1").arg(filename));
1808 
1809  QFile f(filename);
1810  if (!f.open(QIODevice::ReadOnly))
1811  {
1812  LOG(VB_GENERAL, LOG_WARNING, QString("MythCookieJar::load() failed to open file for reading: %1").arg(filename));
1813  return;
1814  }
1815 
1816  QList<QNetworkCookie> cookieList;
1817  QTextStream stream(&f);
1818  while (!stream.atEnd())
1819  {
1820  QString cookie = stream.readLine();
1821  cookieList << QNetworkCookie::parseCookies(cookie.toLocal8Bit());
1822  }
1823 
1824  setAllCookies(cookieList);
1825 }
1826 
1830 void MythCookieJar::save(const QString &filename)
1831 {
1832  LOG(VB_GENERAL, LOG_DEBUG, QString("MythCookieJar: saving cookies to: %1").arg(filename));
1833 
1834  QFile f(filename);
1835  if (!f.open(QIODevice::WriteOnly))
1836  {
1837  LOG(VB_GENERAL, LOG_ERR, QString("MythCookieJar::save() failed to open file for writing: %1").arg(filename));
1838  return;
1839  }
1840 
1841  QList<QNetworkCookie> cookieList = allCookies();
1842  QTextStream stream(&f);
1843 
1844  for (QList<QNetworkCookie>::iterator it = cookieList.begin();
1845  it != cookieList.end(); ++it)
1846  {
1847  stream << (*it).toRawForm() << endl;
1848  }
1849 }
1850 
1851 
1852 /* vim: set expandtab tabstop=4 shiftwidth=4: */
void RunEpilog(void)
Cleans up a thread&#39;s resources, call this if you reimplement run().
Definition: mthread.cpp:216
QNetworkCookieJar * copyCookieJar(void)
Copy from one cookie jar to another.
QNetworkReply::NetworkError m_errorCode
void start(QThread::Priority=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:291
void save(const QString &filename)
Saves the cookie jar to a cookie file.
QList< MythDownloadInfo * > m_cancellationQueue
MythDownloadManager * m_parent
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:46
MRequestType
void queueItem(const QString &url, QNetworkRequest *req, const QString &dest, QByteArray *data, QObject *caller, const MRequestType reqType=kRequestGet, const bool reload=false)
Adds a request to the download queue.
QMap< QString, MythDownloadInfo * > m_downloadInfos
MythDownloadManager * downloadManager
void start(QRunnable *runnable, QString debugName, int priority=0)
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:89
void removeListener(QObject *caller)
Disconnects the specified caller from any existing MythDownloadInfo instances.
void run(void)
Runs a loop to process incoming download requests and triggers download events to be processed...
QNetworkDiskCache * m_diskCache
void queueDownload(const QString &url, const QString &dest, QObject *caller, const bool reload=false)
Adds a url to the download queue.
~MythDownloadManager()
Destructor for MythDownloadManager.
static void error(const char *str,...)
Definition: vbi.c:41
void ShutdownMythDownloadManager(void)
Deletes the running MythDownloadManager at program exit.
#define CACHE_REDIRECTION_LIMIT
A subclassed QNetworkCookieJar that allows for reading and writing cookie files that contain raw form...
void downloadQNetworkRequest(MythDownloadInfo *dlInfo)
Downloads a QNetworkRequest via the QNetworkAccessManager.
bool wait(unsigned long time=ULONG_MAX)
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:308
bool saveFile(const QString &outFile, const QByteArray &data, const bool append=false)
Saves a QByteArray of data to a given filename.
QNetworkReply * m_reply
const char * filename
Definition: ioapi.h:135
QList< MythDownloadInfo * > m_downloadQueue
unsigned int uint
Definition: compat.h:136
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDownloadManager()
Constructor for MythDownloadManager.
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
Slot to process download update events.
const QHash< QByteArray, QByteArray > * m_headers
friend class RemoteFileDownloadThread
MRequestType m_requestType
void downloadFinished(QNetworkReply *reply)
Slot to process download finished events.
#define off_t
bool downloadAuth(const QString &url, const QString &dest, const bool reload=false, AuthCallback authCallback=NULL, void *authArg=NULL, const QHash< QByteArray, QByteArray > *headers=NULL)
Downloads a URL to a file in blocking mode.
static bool resolveLinkLocal(QString &host, int port, int timeLimit=30000)
Convenience method to resolve link-local address.
QString GetConfDir(void)
Definition: mythdirs.cpp:224
void authCallback(QNetworkReply *reply, QAuthenticator *authenticator)
Signal handler for authentication requests.
QNetworkAccessManager * m_manager
bool postAuth(const QString &url, QByteArray *data, AuthCallback authCallback, void *authArg, const QHash< QByteArray, QByteArray > *headers=NULL)
Posts data to a url via the QNetworkAccessManager.
voidpf uLong offset
Definition: ioapi.h:142
This class is used as a container for messages.
Definition: mythevent.h:15
VERBOSE_PREAMBLE false
Definition: verbosedefs.h:83
QUrl redirectUrl(const QUrl &possibleRedirectUrl, const QUrl &oldRedirectUrl) const
Checks whether we were redirected to the given URL.
void setCookieJar(QNetworkCookieJar *cookieJar)
RemoteFileDownloadThread(MythDownloadManager *parent, MythDownloadInfo *dlInfo)
QWaitCondition m_queueWaitCond
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
void queuePost(const QString &url, QByteArray *data, QObject *caller)
Queues a post to a URL via the QNetworkAccessManager.
bool processItem(const QString &url, QNetworkRequest *req, const QString &dest, QByteArray *data, const MRequestType reqType=kRequestGet, const bool reload=false, AuthCallback authCallback=NULL, void *authArg=NULL, const QHash< QByteArray, QByteArray > *headers=NULL)
Processes a network request immediately and waits for a response.
void refreshCookieJar(QNetworkCookieJar *jar)
Refresh the temporary cookie jar from another cookie jar.
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
bool download(const QString &url, const QString &dest, const bool reload=false)
Downloads a URL to a file in blocking mode.
QDateTime GetLastModified(const QString &url)
Gets the Last Modified timestamp for a URI.
QNetworkCookieJar * m_inCookieJar
void downloadError(QNetworkReply::NetworkError errorCode)
Slot to process download error events.
#define LOC
void(* AuthCallback)(QNetworkReply *, QAuthenticator *, void *)
QString getHeader(const QUrl &url, const QString &header)
static MThreadPool * globalInstance(void)
bool SaveAs(QByteArray &data)
void downloadRemoteFile(MythDownloadInfo *dlInfo)
Triggers a myth:// URI download in the background via RemoteFile.
voidpf stream
Definition: ioapi.h:136
void SetDone(bool done)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:38
QMap< QNetworkReply *, MythDownloadInfo * > m_downloadReplies
QNetworkRequest * m_request
void updateCookieJar(void)
Update the cookie jar from the temporary cookie jar.
void RunProlog(void)
Sets up a thread, call this if you reimplement run().
Definition: mthread.cpp:203
bool downloadNow(MythDownloadInfo *dlInfo, bool deleteInfo=true)
Download helper for download() blocking methods.
#define MYTH_BINARY_VERSION
Update this whenever the plug-in ABI changes.
Definition: mythversion.h:15
void cancelDownload(const QString &url, bool block=true)
Cancel a queued or current download.
bool post(const QString &url, QByteArray *data)
Posts data to a url via the QNetworkAccessManager.
const char int mode
Definition: ioapi.h:135
static void usleep(unsigned long time)
Definition: mthread.cpp:345
QThread * getQueueThread(void)
QMutex dmCreateLock
QString GetHostName(void)
void saveCookieJar(const QString &filename)
Saves the cookie jar to a cookie file.
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:34
void loadCookieJar(const QString &filename)
Loads the cookie jar from a cookie file.
MythCookieJar()
Creates an empty MythCookieJar.
void load(const QString &filename)
Loads the cookie jar from a cookie file.
void preCache(const QString &url)
Downloads a URL but doesn&#39;t store the resulting data anywhere.