MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
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 
15 #include "stdlib.h"
16 
17 // libmythbase
18 #include "compat.h"
19 #include "mythcorecontext.h"
20 #include "mythcoreutil.h"
21 #include "mthreadpool.h"
22 #include "mythdirs.h"
23 #include "mythevent.h"
24 #include "mythversion.h"
25 #include "remotefile.h"
26 #include "mythdate.h"
27 
28 #include "mythdownloadmanager.h"
29 #include "mythlogging.h"
30 #include <QUrl>
31 
32 using namespace std;
33 
34 #define LOC QString("DownloadManager: ")
35 #define CACHE_REDIRECTION_LIMIT 10
36 
38 QMutex dmCreateLock;
39 
44 {
45  public:
47  m_request(NULL), m_reply(NULL), m_data(NULL),
48  m_caller(NULL), m_requestType(kRequestGet),
49  m_reload(false), m_preferCache(false), m_syncMode(false),
50  m_processReply(true), m_done(false), m_bytesReceived(0),
51  m_bytesTotal(0), m_lastStat(MythDate::current()),
52  m_authCallback(NULL), m_authArg(NULL),
53  m_headers(NULL), m_errorCode(QNetworkReply::NoError)
54  {
55  qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
56  }
57 
59  {
60  if (m_request)
61  delete m_request;
62  if (m_reply && m_processReply)
63  m_reply->deleteLater();
64  }
65 
66  void detach(void)
67  {
68  m_url.detach();
69  m_outFile.detach();
70  }
71 
72  bool IsDone(void)
73  {
74  QMutexLocker lock(&m_lock);
75  return m_done;
76  }
77 
78  void SetDone(bool done)
79  {
80  QMutexLocker lock(&m_lock);
81  m_done = done;
82  }
83 
84  QString m_url;
86  QNetworkRequest *m_request;
87  QNetworkReply *m_reply;
88  QString m_outFile;
89  QByteArray *m_data;
90  QByteArray m_privData;
91  QObject *m_caller;
93  bool m_reload;
95  bool m_syncMode;
97  bool m_done;
99  qint64 m_bytesTotal;
100  QDateTime m_lastStat;
102  void *m_authArg;
103  const QHash<QByteArray, QByteArray> *m_headers;
104 
105  QNetworkReply::NetworkError m_errorCode;
106  QMutex m_lock;
107 };
108 
109 
114 class MythCookieJar : public QNetworkCookieJar
115 {
116  public:
117  MythCookieJar();
119  void load(const QString &filename);
120  void save(const QString &filename);
121 };
122 
126 class RemoteFileDownloadThread : public QRunnable
127 {
128  public:
130  MythDownloadInfo *dlInfo) :
131  m_parent(parent),
132  m_dlInfo(dlInfo)
133  {
134  m_dlInfo->detach();
135  }
136 
137  void run()
138  {
139  bool ok = false;
140 
141  RemoteFile *rf = new RemoteFile(m_dlInfo->m_url, false, false, 0);
142  ok = rf->SaveAs(m_dlInfo->m_privData);
143  delete rf;
144 
145  if (!ok)
146  m_dlInfo->m_errorCode = QNetworkReply::UnknownNetworkError;
147 
148  m_dlInfo->m_bytesReceived = m_dlInfo->m_privData.size();
149  m_dlInfo->m_bytesTotal = m_dlInfo->m_bytesReceived;
150 
151  m_parent->downloadFinished(m_dlInfo);
152  }
153 
154  private:
157 };
158 
162 {
163  if (downloadManager)
164  {
165  delete downloadManager;
166  downloadManager = NULL;
167  }
168 }
169 
174 {
175  if (downloadManager)
176  return downloadManager;
177 
178  QMutexLocker locker(&dmCreateLock);
179 
180  // Check once more in case the download manager was created
181  // while we were securing the lock.
182  if (downloadManager)
183  return downloadManager;
184 
186  tmpDLM->start();
187  while (!tmpDLM->getQueueThread())
188  usleep(10000);
189 
190  tmpDLM->moveToThread(tmpDLM->getQueueThread());
191  tmpDLM->setRunThread();
192 
193  while (!tmpDLM->isRunning())
194  usleep(10000);
195 
196  downloadManager = tmpDLM;
197 
199 
200  return downloadManager;
201 }
202 
207  MThread("DownloadManager"),
208  m_manager(NULL),
209  m_diskCache(NULL),
210  m_proxy(NULL),
211  m_infoLock(new QMutex(QMutex::Recursive)),
212  m_queueThread(NULL),
213  m_runThread(false),
214  m_isRunning(false),
215  m_inCookieJar(NULL)
216 {
217 }
218 
222 {
223  m_runThread = false;
224  m_queueWaitCond.wakeAll();
225 
226  wait();
227 
228  delete m_infoLock;
229 
230  if (m_inCookieJar)
231  delete m_inCookieJar;
232 }
233 
238 {
239  RunProlog();
240 
241  bool downloading = false;
242  bool itemsInQueue = false;
243  bool waitAnyway = false;
244 
245  m_queueThread = QThread::currentThread();
246 
247  while (!m_runThread)
248  usleep(50000);
249 
250  m_manager = new QNetworkAccessManager(this);
251  m_diskCache = new QNetworkDiskCache(this);
252  m_proxy = new QNetworkProxy();
253  m_diskCache->setCacheDirectory(GetConfDir() + "/Cache-" +
254  QCoreApplication::applicationName() + "-" +
256  m_manager->setCache(m_diskCache);
257 
258  // Set the proxy for the manager to be the application default proxy,
259  // which has already been setup
260  m_manager->setProxy(*m_proxy);
261 
262  // make sure the cookieJar is created in the same thread as the manager
263  // and set its parent to NULL so it can be shared between managers
264  m_manager->cookieJar()->setParent(NULL);
265 
266  QObject::connect(m_manager, SIGNAL(finished(QNetworkReply*)), this,
267  SLOT(downloadFinished(QNetworkReply*)));
268 
269  m_isRunning = true;
270  while (m_runThread)
271  {
272  if (m_inCookieJar)
273  {
274  LOG(VB_GENERAL, LOG_DEBUG, "Updating DLManager's Cookie Jar");
275  updateCookieJar();
276  }
277  m_infoLock->lock();
278  downloading = !m_downloadInfos.isEmpty();
279  itemsInQueue = !m_downloadQueue.isEmpty();
280  m_infoLock->unlock();
281 
282  if (downloading)
283  QCoreApplication::processEvents();
284 
285  if (!itemsInQueue || waitAnyway)
286  {
287  waitAnyway = false;
288  m_queueWaitLock.lock();
289 
290  if (downloading)
291  m_queueWaitCond.wait(&m_queueWaitLock, 200);
292  else
294 
295  m_queueWaitLock.unlock();
296  }
297 
298  m_infoLock->lock();
299  if (!m_downloadQueue.isEmpty())
300  {
301  MythDownloadInfo *dlInfo = m_downloadQueue.front();
302 
303  m_downloadQueue.pop_front();
304 
305  if (!dlInfo)
306  continue;
307 
308  QUrl qurl(dlInfo->m_url);
309  if (m_downloadInfos.contains(qurl.toString()))
310  {
311  // Push request to the end of the queue to let others process.
312  // If this is the only item in the queue, force the loop to
313  // wait a little.
314  if (m_downloadQueue.isEmpty())
315  waitAnyway = true;
316  m_downloadQueue.push_back(dlInfo);
317  m_infoLock->unlock();
318  continue;
319  }
320 
321  if (dlInfo->m_url.startsWith("myth://"))
322  downloadRemoteFile(dlInfo);
323  else
324  {
325  QMutexLocker cLock(&m_cookieLock);
326  downloadQNetworkRequest(dlInfo);
327  }
328 
329  m_downloadInfos[qurl.toString()] = dlInfo;
330  }
331  m_infoLock->unlock();
332  }
333  m_isRunning = false;
334 
335  RunEpilog();
336 }
337 
348 void MythDownloadManager::queueItem(const QString &url, QNetworkRequest *req,
349  const QString &dest, QByteArray *data,
350  QObject *caller, const MRequestType reqType,
351  const bool reload)
352 {
353  MythDownloadInfo *dlInfo = new MythDownloadInfo;
354 
355  dlInfo->m_url = url;
356  dlInfo->m_request = req;
357  dlInfo->m_outFile = dest;
358  dlInfo->m_data = data;
359  dlInfo->m_caller = caller;
360  dlInfo->m_requestType = reqType;
361  dlInfo->m_reload = reload;
362 
363  dlInfo->detach();
364 
365  QMutexLocker locker(m_infoLock);
366  m_downloadQueue.push_back(dlInfo);
367  m_queueWaitCond.wakeAll();
368 }
369 
382 bool MythDownloadManager::processItem(const QString &url, QNetworkRequest *req,
383  const QString &dest, QByteArray *data,
384  const MRequestType reqType,
385  const bool reload,
386  AuthCallback authCallback, void *authArg,
387  const QHash<QByteArray, QByteArray> *headers)
388 {
389  MythDownloadInfo *dlInfo = new MythDownloadInfo;
390 
391  dlInfo->m_url = url;
392  dlInfo->m_request = req;
393  dlInfo->m_outFile = dest;
394  dlInfo->m_data = data;
395  dlInfo->m_requestType = reqType;
396  dlInfo->m_reload = reload;
397  dlInfo->m_syncMode = true;
398  dlInfo->m_authCallback = authCallback;
399  dlInfo->m_authArg = authArg;
400  dlInfo->m_headers = headers;
401 
402  return downloadNow(dlInfo);
403 }
404 
408 void MythDownloadManager::preCache(const QString &url)
409 {
410  LOG(VB_FILE, LOG_DEBUG, LOC + QString("preCache('%1')").arg(url));
411  queueItem(url, NULL, QString(), NULL, NULL);
412 }
413 
420 void MythDownloadManager::queueDownload(const QString &url,
421  const QString &dest,
422  QObject *caller,
423  const bool reload)
424 {
425  LOG(VB_FILE, LOG_DEBUG, LOC + QString("queueDownload('%1', '%2', %3)")
426  .arg(url).arg(dest).arg((long long)caller));
427 
428  queueItem(url, NULL, dest, NULL, caller, kRequestGet, reload);
429 }
430 
436 void MythDownloadManager::queueDownload(QNetworkRequest *req,
437  QByteArray *data,
438  QObject *caller)
439 {
440  LOG(VB_FILE, LOG_DEBUG, LOC + QString("queueDownload('%1', '%2', %3)")
441  .arg(req->url().toString()).arg((long long)data)
442  .arg((long long)caller));
443 
444  queueItem(req->url().toString(), req, QString(), data, caller);
445 }
446 
453 bool MythDownloadManager::download(const QString &url, const QString &dest,
454  const bool reload)
455 {
456  return processItem(url, NULL, dest, NULL, kRequestGet, reload);
457 }
458 
465 bool MythDownloadManager::download(const QString &url, QByteArray *data,
466  const bool reload)
467 {
468  return processItem(url, NULL, QString(), data, kRequestGet, reload);
469 }
470 
477 QNetworkReply *MythDownloadManager::download(const QString &url,
478  const bool reload)
479 {
480  MythDownloadInfo *dlInfo = new MythDownloadInfo;
481 
482  dlInfo->m_url = url;
483  dlInfo->m_reload = reload;
484  dlInfo->m_syncMode = true;
485  dlInfo->m_processReply = false;
486 
487  bool ok = downloadNow(dlInfo, false);
488 
489  QNetworkReply *reply = dlInfo->m_reply;
490 
491  if (reply)
492  dlInfo->m_reply = NULL;
493 
494  delete dlInfo;
495  dlInfo = NULL;
496 
497  if (ok && reply)
498  return reply;
499 
500  return NULL;
501 }
502 
508 bool MythDownloadManager::download(QNetworkRequest *req, QByteArray *data)
509 {
510  LOG(VB_FILE, LOG_DEBUG, LOC + QString("download('%1', '%2')")
511  .arg(req->url().toString()).arg((long long)data));
512  return processItem(req->url().toString(), req, QString(), data);
513 }
514 
524 bool MythDownloadManager::downloadAuth(const QString &url, const QString &dest,
525  const bool reload, AuthCallback authCallback, void *authArg,
526  const QHash<QByteArray, QByteArray> *headers)
527 {
528  return processItem(url, NULL, dest, NULL, kRequestGet, reload, authCallback,
529  authArg, headers);
530 }
531 
532 
538 void MythDownloadManager::queuePost(const QString &url,
539  QByteArray *data,
540  QObject *caller)
541 {
542  LOG(VB_FILE, LOG_DEBUG, LOC + QString("queuePost('%1', '%2')")
543  .arg(url).arg((long long)data));
544 
545  if (!data)
546  {
547  LOG(VB_GENERAL, LOG_ERR, LOC + "queuePost(), data is NULL!");
548  return;
549  }
550 
551  queueItem(url, NULL, QString(), data, caller, kRequestPost);
552 }
553 
559 void MythDownloadManager::queuePost(QNetworkRequest *req,
560  QByteArray *data,
561  QObject *caller)
562 {
563  LOG(VB_FILE, LOG_DEBUG, LOC + QString("queuePost('%1', '%2')")
564  .arg(req->url().toString()).arg((long long)data));
565 
566  if (!data)
567  {
568  LOG(VB_GENERAL, LOG_ERR, LOC + "queuePost(), data is NULL!");
569  return;
570  }
571 
572  queueItem(req->url().toString(), req, QString(), data, caller,
573  kRequestPost);
574 }
575 
581 bool MythDownloadManager::post(const QString &url, QByteArray *data)
582 {
583  LOG(VB_FILE, LOG_DEBUG, LOC + QString("post('%1', '%2')")
584  .arg(url).arg((long long)data));
585 
586  if (!data)
587  {
588  LOG(VB_GENERAL, LOG_ERR, LOC + "post(), data is NULL!");
589  return false;
590  }
591 
592  return processItem(url, NULL, QString(), data, kRequestPost);
593 }
594 
600 bool MythDownloadManager::post(QNetworkRequest *req, QByteArray *data)
601 {
602  LOG(VB_FILE, LOG_DEBUG, LOC + QString("post('%1', '%2')")
603  .arg(req->url().toString()).arg((long long)data));
604 
605  if (!data)
606  {
607  LOG(VB_GENERAL, LOG_ERR, LOC + "post(), data is NULL!");
608  return false;
609  }
610 
611  return processItem(req->url().toString(), req, QString(), data,
612  kRequestPost);
613 }
614 
623 bool MythDownloadManager::postAuth(const QString &url, QByteArray *data,
624  AuthCallback authCallback, 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, NULL, NULL, data, kRequestPost, false, authCallback,
637  authArg, headers);
638 }
639 
644 {
645  RemoteFileDownloadThread *dlThread =
646  new RemoteFileDownloadThread(this, dlInfo);
647  MThreadPool::globalInstance()->start(dlThread, "RemoteFileDownload");
648 }
649 
654 {
655  if (!dlInfo)
656  return;
657 
658  static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
659  QUrl qurl(dlInfo->m_url);
660  QNetworkRequest request;
661 
662  if (dlInfo->m_request)
663  {
664  request = *dlInfo->m_request;
665  delete dlInfo->m_request;
666  dlInfo->m_request = NULL;
667  }
668  else
669  request.setUrl(qurl);
670 
671  if (!dlInfo->m_reload)
672  {
673  // Prefer the in-cache item if one exists and it is less than 5 minutes
674  // old and it will not expire in the next 10 seconds
675  QDateTime now = MythDate::current();
676 
677  // Handle redirects, we want the metadata of the file headers
678  QString redirectLoc;
679  int limit = 0;
680  while (!(redirectLoc = getHeader(qurl, "Location")).isNull())
681  {
682  if (limit == CACHE_REDIRECTION_LIMIT)
683  {
684  LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
685  "reached for %1")
686  .arg(qurl.toString()));
687  return;
688  }
689  qurl.setUrl(redirectLoc);
690  limit++;
691  }
692 
693  LOG(VB_NETWORK, LOG_DEBUG, QString("Checking cache for %1")
694  .arg(qurl.toString()));
695 
696  m_infoLock->lock();
697  QNetworkCacheMetaData urlData = m_manager->cache()->metaData(qurl);
698  m_infoLock->unlock();
699  if ((urlData.isValid()) &&
700  ((!urlData.expirationDate().isValid()) ||
701  (QDateTime(urlData.expirationDate().toUTC()).secsTo(now) < 10)))
702  {
703  QString dateString = getHeader(urlData, "Date");
704 
705  if (!dateString.isNull())
706  {
707  QDateTime loadDate =
708  MythDate::fromString(dateString, dateFormat);
709  loadDate.setTimeSpec(Qt::UTC);
710  if (loadDate.secsTo(now) <= 720)
711  {
712  dlInfo->m_preferCache = true;
713  LOG(VB_NETWORK, LOG_DEBUG, QString("Preferring cache for %1")
714  .arg(qurl.toString()));
715  }
716  }
717  }
718  }
719 
720  if (dlInfo->m_preferCache)
721  request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
722  QNetworkRequest::PreferCache);
723 
724  request.setRawHeader("User-Agent",
725  "MythTV v" MYTH_BINARY_VERSION " MythDownloadManager");
726 
727  if (dlInfo->m_headers)
728  {
729  QHash<QByteArray, QByteArray>::const_iterator it =
730  dlInfo->m_headers->constBegin();
731  for ( ; it != dlInfo->m_headers->constEnd(); ++it )
732  {
733  if (!it.key().isEmpty() && !it.value().isEmpty())
734  {
735  request.setRawHeader(it.key(), it.value());
736  }
737  }
738  }
739 
740  switch (dlInfo->m_requestType)
741  {
742  case kRequestPost :
743  dlInfo->m_reply = m_manager->post(request, *dlInfo->m_data);
744  break;
745  case kRequestHead :
746  dlInfo->m_reply = m_manager->head(request);
747  break;
748  case kRequestGet :
749  default:
750  dlInfo->m_reply = m_manager->get(request);
751  break;
752  }
753 
754  m_downloadReplies[dlInfo->m_reply] = dlInfo;
755 
756  if (dlInfo->m_authCallback)
757  {
758  connect(m_manager, SIGNAL(authenticationRequired(QNetworkReply *,
759  QAuthenticator *)),
760  this, SLOT(authCallback(QNetworkReply *, QAuthenticator *)));
761  }
762 
763  connect(dlInfo->m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
764  SLOT(downloadError(QNetworkReply::NetworkError)));
765  connect(dlInfo->m_reply, SIGNAL(downloadProgress(qint64, qint64)),
766  this, SLOT(downloadProgress(qint64, qint64)));
767 }
768 
773 void MythDownloadManager::authCallback(QNetworkReply *reply,
774  QAuthenticator *authenticator)
775 {
776  if (!reply)
777  return;
778 
779  MythDownloadInfo *dlInfo = m_downloadReplies[reply];
780 
781  if (!dlInfo)
782  return;
783 
784  if (dlInfo->m_authCallback)
785  {
786  LOG(VB_FILE, LOG_DEBUG, "Calling auth callback");
787  dlInfo->m_authCallback(reply, authenticator, dlInfo->m_authArg);
788  }
789 }
790 
798 {
799  if (!dlInfo)
800  return false;
801 
802  dlInfo->m_syncMode = true;
803 
804  m_infoLock->lock();
805  m_downloadQueue.push_back(dlInfo);
806  m_infoLock->unlock();
807  m_queueWaitCond.wakeAll();
808 
809  // timeout myth:// RemoteFile transfers 20 seconds from now
810  // timeout non-myth:// QNetworkAccessManager transfers 10 seconds after
811  // their last progress update
812  QDateTime startedAt = MythDate::current();
813  m_infoLock->lock();
814  while ((!dlInfo->IsDone()) &&
815  (dlInfo->m_errorCode == QNetworkReply::NoError) &&
816  (((!dlInfo->m_url.startsWith("myth://")) &&
817  (dlInfo->m_lastStat.secsTo(MythDate::current()) < 60)) ||
818  ((dlInfo->m_url.startsWith("myth://")) &&
819  (startedAt.secsTo(MythDate::current()) < 20))))
820  {
821  m_infoLock->unlock();
822  m_queueWaitLock.lock();
823  m_queueWaitCond.wait(&m_queueWaitLock, 200);
824  m_queueWaitLock.unlock();
825  m_infoLock->lock();
826  }
827  bool done = dlInfo->IsDone();
828  bool success =
829  done && (dlInfo->m_errorCode == QNetworkReply::NoError);
830 
831  if (!done)
832  {
833  dlInfo->m_data = NULL; // Prevent downloadFinished() from updating
834  dlInfo->m_syncMode = false; // Let downloadFinished() cleanup for us
835  if ((dlInfo->m_reply) &&
836  (dlInfo->m_errorCode == QNetworkReply::NoError))
837  {
838  LOG(VB_FILE, LOG_DEBUG,
839  LOC + QString("Aborting download - lack of data transfer"));
840  dlInfo->m_reply->abort();
841  }
842  }
843  else if (deleteInfo)
844  {
845  delete dlInfo;
846  dlInfo = NULL;
847  }
848 
849  m_infoLock->unlock();
850 
851  return success;
852 }
853 
857 void MythDownloadManager::cancelDownload(const QString &url)
858 {
859  QMutexLocker locker(m_infoLock);
860  MythDownloadInfo *dlInfo;
861 
862  QMutableListIterator<MythDownloadInfo*> lit(m_downloadQueue);
863  while (lit.hasNext())
864  {
865  lit.next();
866  dlInfo = lit.value();
867  if (dlInfo->m_url == url)
868  {
869  // this shouldn't happen
870  if (dlInfo->m_reply)
871  {
872  LOG(VB_FILE, LOG_DEBUG,
873  LOC + QString("Aborting download - user request"));
874  dlInfo->m_reply->abort();
875  }
876  lit.remove();
877  dlInfo->m_lock.lock();
878  dlInfo->m_errorCode = QNetworkReply::OperationCanceledError;
879  dlInfo->m_done = true;
880  dlInfo->m_lock.unlock();
881  }
882  }
883 
884  if (m_downloadInfos.contains(url))
885  {
886  dlInfo = m_downloadInfos[url];
887  if (dlInfo->m_reply)
888  {
889  LOG(VB_FILE, LOG_DEBUG,
890  LOC + QString("Aborting download - user request"));
891  m_downloadReplies.remove(dlInfo->m_reply);
892  dlInfo->m_reply->abort();
893  }
894  m_downloadInfos.remove(url);
895  dlInfo->m_lock.lock();
896  dlInfo->m_errorCode = QNetworkReply::OperationCanceledError;
897  dlInfo->m_done = true;
898  dlInfo->m_lock.unlock();
899  }
900 }
901 
907 {
908  QMutexLocker locker(m_infoLock);
909  MythDownloadInfo *dlInfo;
910 
911  QList <MythDownloadInfo*>::iterator lit = m_downloadQueue.begin();
912  for (; lit != m_downloadQueue.end(); ++lit)
913  {
914  dlInfo = *lit;
915  if (dlInfo->m_caller == caller)
916  {
917  dlInfo->m_caller = NULL;
918  dlInfo->m_outFile = QString();
919  dlInfo->m_data = NULL;
920  }
921  }
922 
923  QMap <QString, MythDownloadInfo*>::iterator mit = m_downloadInfos.begin();
924  for (; mit != m_downloadInfos.end(); ++mit)
925  {
926  dlInfo = mit.value();
927  if (dlInfo->m_caller == caller)
928  {
929  dlInfo->m_caller = NULL;
930  dlInfo->m_outFile = QString();
931  dlInfo->m_data = NULL;
932  }
933  }
934 }
935 
939 void MythDownloadManager::downloadError(QNetworkReply::NetworkError errorCode)
940 {
941  QNetworkReply *reply = (QNetworkReply*)sender();
942 
943  LOG(VB_FILE, LOG_DEBUG, LOC + QString("downloadError %1 ")
944  .arg(errorCode) + reply->errorString() );
945 
946  QMutexLocker locker(m_infoLock);
947  if (!m_downloadReplies.contains(reply))
948  {
949  reply->deleteLater();
950  return;
951  }
952 
953  MythDownloadInfo *dlInfo = m_downloadReplies[reply];
954 
955  if (!dlInfo)
956  return;
957 
958  dlInfo->m_errorCode = errorCode;
959 }
960 
966 QUrl MythDownloadManager::redirectUrl(const QUrl& possibleRedirectUrl,
967  const QUrl& oldRedirectUrl) const
968 {
969  LOG(VB_FILE, LOG_DEBUG, LOC + QString("redirectUrl()"));
970  QUrl redirectUrl;
971 
972  if(!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != oldRedirectUrl)
973  redirectUrl = possibleRedirectUrl;
974 
975  return redirectUrl;
976 }
977 
981 void MythDownloadManager::downloadFinished(QNetworkReply* reply)
982 {
983  LOG(VB_FILE, LOG_DEBUG, LOC + QString("downloadFinished(%1)")
984  .arg((long long)reply));
985 
986  QMutexLocker locker(m_infoLock);
987  if (!m_downloadReplies.contains(reply))
988  {
989  reply->deleteLater();
990  return;
991  }
992 
993  MythDownloadInfo *dlInfo = m_downloadReplies[reply];
994 
995  if (!dlInfo || !dlInfo->m_reply)
996  return;
997 
998  downloadFinished(dlInfo);
999 }
1000 
1005 {
1006  if (!dlInfo)
1007  return;
1008 
1009  static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
1010  QNetworkReply *reply = dlInfo->m_reply;
1011 
1012  if (reply)
1013  {
1014  QUrl possibleRedirectUrl =
1015  reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
1016 
1017  dlInfo->m_redirectedTo =
1018  redirectUrl(possibleRedirectUrl, dlInfo->m_redirectedTo);
1019  }
1020 
1021  if(!dlInfo->m_redirectedTo.isEmpty())
1022  {
1023  LOG(VB_FILE, LOG_DEBUG, LOC +
1024  QString("downloadFinished(%1): Redirect: %2 -> %3")
1025  .arg((long long)dlInfo)
1026  .arg(reply->url().toString())
1027  .arg(dlInfo->m_redirectedTo.toString()));
1028 
1029  QNetworkRequest request(dlInfo->m_redirectedTo);
1030  if (dlInfo->m_preferCache)
1031  request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
1032  QNetworkRequest::PreferCache);
1033  request.setRawHeader("User-Agent",
1034  "MythDownloadManager v" MYTH_BINARY_VERSION);
1035 
1036  switch (dlInfo->m_requestType)
1037  {
1038  case kRequestPost :
1039  dlInfo->m_reply = m_manager->post(request, *dlInfo->m_data);
1040  break;
1041  case kRequestHead :
1042  dlInfo->m_reply = m_manager->head(request);
1043  break;
1044  case kRequestGet :
1045  default:
1046  dlInfo->m_reply = m_manager->get(request);
1047  break;
1048  }
1049 
1050  m_downloadReplies[dlInfo->m_reply] = dlInfo;
1051 
1052  connect(dlInfo->m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
1053  this, SLOT(downloadError(QNetworkReply::NetworkError)));
1054  connect(dlInfo->m_reply, SIGNAL(downloadProgress(qint64, qint64)),
1055  this, SLOT(downloadProgress(qint64, qint64)));
1056 
1057  m_downloadReplies.remove(reply);
1058  reply->deleteLater();
1059  }
1060  else
1061  {
1062  LOG(VB_FILE, LOG_DEBUG, QString("downloadFinished(%1): COMPLETE: %2")
1063  .arg((long long)dlInfo).arg(dlInfo->m_url));
1064 
1065  // HACK Insert a Date header into the cached metadata if one doesn't
1066  // already exist
1067  QUrl fileUrl = dlInfo->m_url;
1068  QString redirectLoc;
1069  int limit = 0;
1070  while (!(redirectLoc = getHeader(fileUrl, "Location")).isNull())
1071  {
1072  if (limit == CACHE_REDIRECTION_LIMIT)
1073  {
1074  LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
1075  "reached for %1")
1076  .arg(fileUrl.toString()));
1077  return;
1078  }
1079  fileUrl.setUrl(redirectLoc);
1080  limit++;
1081  }
1082 
1083  m_infoLock->lock();
1084  QNetworkCacheMetaData urlData = m_manager->cache()->metaData(fileUrl);
1085  m_infoLock->unlock();
1086  if (getHeader(urlData, "Date").isNull())
1087  {
1088  QNetworkCacheMetaData::RawHeaderList headers = urlData.rawHeaders();
1089  QNetworkCacheMetaData::RawHeader newheader;
1090  QDateTime now = MythDate::current();
1091  newheader = QNetworkCacheMetaData::RawHeader("Date",
1092  now.toString(dateFormat).toLatin1());
1093  headers.append(newheader);
1094  urlData.setRawHeaders(headers);
1095  m_infoLock->lock();
1096  m_manager->cache()->updateMetaData(urlData);
1097  m_infoLock->unlock();
1098  }
1099  // End HACK
1100 
1101  dlInfo->m_redirectedTo.clear();
1102 
1103  int dataSize = -1;
1104 
1105  // If we downloaded via the QNetworkAccessManager
1106  // AND the caller isn't handling the reply directly
1107  if (reply && dlInfo->m_processReply)
1108  {
1109  bool append = (!dlInfo->m_syncMode && dlInfo->m_caller);
1110  QByteArray data = reply->readAll();
1111  dataSize = data.size();
1112 
1113  if (append)
1114  dlInfo->m_bytesReceived += dataSize;
1115  else
1116  dlInfo->m_bytesReceived = dataSize;
1117 
1118  dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
1119 
1120  if (dlInfo->m_data)
1121  {
1122  if (append)
1123  dlInfo->m_data->append(data);
1124  else
1125  *dlInfo->m_data = data;
1126  }
1127  else if (!dlInfo->m_outFile.isEmpty())
1128  {
1129  saveFile(dlInfo->m_outFile, data, append);
1130  }
1131  }
1132  else if (!reply) // If we downloaded via RemoteFile
1133  {
1134  if (dlInfo->m_data)
1135  {
1136  (*dlInfo->m_data) = dlInfo->m_privData;
1137  }
1138  else if (!dlInfo->m_outFile.isEmpty())
1139  {
1140  saveFile(dlInfo->m_outFile, dlInfo->m_privData);
1141  }
1142  dlInfo->m_bytesReceived += dataSize;
1143  dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
1144  }
1145  // else we downloaded via QNetworkAccessManager
1146  // AND the caller is handling the reply
1147 
1148  m_downloadInfos.remove(dlInfo->m_url);
1149  if (reply)
1150  m_downloadReplies.remove(reply);
1151 
1152  dlInfo->SetDone(true);
1153 
1154  if (!dlInfo->m_syncMode)
1155  {
1156  if (dlInfo->m_caller)
1157  {
1158  LOG(VB_FILE, LOG_DEBUG, QString("downloadFinished(%1): "
1159  "COMPLETE: %2, sending event to caller")
1160  .arg((long long)dlInfo).arg(dlInfo->m_url));
1161 
1162  QStringList args;
1163  args << dlInfo->m_url;
1164  args << dlInfo->m_outFile;
1165  args << QString::number(dlInfo->m_bytesTotal);
1166  // placeholder for error string
1167  args << (reply ? reply->errorString() : QString());
1168  args << QString::number((int)(reply ? reply->error() :
1169  dlInfo->m_errorCode));
1170 
1171  QCoreApplication::postEvent(dlInfo->m_caller,
1172  new MythEvent("DOWNLOAD_FILE FINISHED", args));
1173  }
1174 
1175  delete dlInfo;
1176  dlInfo = NULL;
1177  }
1178 
1179  m_queueWaitCond.wakeAll();
1180  }
1181 }
1182 
1188 void MythDownloadManager::downloadProgress(qint64 bytesReceived,
1189  qint64 bytesTotal)
1190 {
1191  QNetworkReply *reply = (QNetworkReply*)sender();
1192 
1193  LOG(VB_FILE, LOG_DEBUG, LOC +
1194  QString("downloadProgress(%1, %2) (for reply %3)")
1195  .arg(bytesReceived).arg(bytesTotal).arg((long long)reply));
1196 
1197  QMutexLocker locker(m_infoLock);
1198  if (!m_downloadReplies.contains(reply))
1199  return;
1200 
1201  MythDownloadInfo *dlInfo = m_downloadReplies[reply];
1202 
1203  if (!dlInfo)
1204  return;
1205 
1206  dlInfo->m_lastStat = MythDate::current();
1207 
1208  LOG(VB_FILE, LOG_DEBUG, LOC +
1209  QString("downloadProgress: %1 to %2 is at %3 of %4 bytes downloaded")
1210  .arg(dlInfo->m_url).arg(dlInfo->m_outFile)
1211  .arg(bytesReceived).arg(bytesTotal));
1212 
1213  if (!dlInfo->m_syncMode && dlInfo->m_caller)
1214  {
1215  LOG(VB_FILE, LOG_DEBUG, QString("downloadProgress(%1): "
1216  "sending event to caller")
1217  .arg(reply->url().toString()));
1218 
1219  bool appendToFile = (dlInfo->m_bytesReceived != 0);
1220  QByteArray data = reply->readAll();
1221  if (!dlInfo->m_outFile.isEmpty())
1222  saveFile(dlInfo->m_outFile, data, appendToFile);
1223 
1224  if (dlInfo->m_data)
1225  dlInfo->m_data->append(data);
1226 
1227  dlInfo->m_bytesReceived = bytesReceived;
1228  dlInfo->m_bytesTotal = bytesTotal;
1229 
1230  QStringList args;
1231  args << dlInfo->m_url;
1232  args << dlInfo->m_outFile;
1233  args << QString::number(bytesReceived);
1234  args << QString::number(bytesTotal);
1235 
1236  QCoreApplication::postEvent(dlInfo->m_caller,
1237  new MythEvent("DOWNLOAD_FILE UPDATE", args));
1238  }
1239 }
1240 
1248 bool MythDownloadManager::saveFile(const QString &outFile,
1249  const QByteArray &data,
1250  const bool append)
1251 {
1252  if (outFile.isEmpty() || !data.size())
1253  return false;
1254 
1255  QFile file(outFile);
1256  QFileInfo fileInfo(outFile);
1257  QDir qdir(fileInfo.absolutePath());
1258 
1259  if (!qdir.exists() && !qdir.mkpath(fileInfo.absolutePath()))
1260  {
1261  LOG(VB_GENERAL, LOG_ERR, QString("Failed to create: '%1'")
1262  .arg(fileInfo.absolutePath()));
1263  return false;
1264  }
1265 
1266  QIODevice::OpenMode mode = QIODevice::Unbuffered|QIODevice::WriteOnly;
1267  if (append)
1268  mode |= QIODevice::Append;
1269 
1270  if (!file.open(mode))
1271  {
1272  LOG(VB_GENERAL, LOG_ERR, QString("Failed to open: '%1'") .arg(outFile));
1273  return false;
1274  }
1275 
1276  off_t offset = 0;
1277  size_t remaining = data.size();
1278  uint failure_cnt = 0;
1279  while ((remaining > 0) && (failure_cnt < 5))
1280  {
1281  ssize_t written = file.write(data.data() + offset, remaining);
1282  if (written < 0)
1283  {
1284  failure_cnt++;
1285  usleep(50000);
1286  continue;
1287  }
1288 
1289  failure_cnt = 0;
1290  offset += written;
1291  remaining -= written;
1292  }
1293 
1294  if (remaining > 0)
1295  return false;
1296 
1297  return true;
1298 }
1299 
1304 QDateTime MythDownloadManager::GetLastModified(const QString &url)
1305 {
1306  // If the header has not expired and
1307  // the last modification date is less than 30 minutes old or if
1308  // the cache object is less than 5 minutes old,
1309  // then use the cached header otherwise redownload the header
1310 
1311  static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
1312  LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1')").arg(url));
1313  QDateTime result;
1314 
1315  QDateTime now = MythDate::current();
1316 
1317  QUrl cacheUrl = QUrl(url);
1318 
1319  // Deal with redirects, we want the cached data for the final url
1320  QString redirectLoc;
1321  int limit = 0;
1322  while (!(redirectLoc = getHeader(cacheUrl, "Location")).isNull())
1323  {
1324  if (limit == CACHE_REDIRECTION_LIMIT)
1325  {
1326  LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
1327  "reached for %1")
1328  .arg(cacheUrl.toString()));
1329  return result;
1330  }
1331  cacheUrl.setUrl(redirectLoc);
1332  limit++;
1333  }
1334 
1335  m_infoLock->lock();
1336  QNetworkCacheMetaData urlData = m_manager->cache()->metaData(cacheUrl);
1337  m_infoLock->unlock();
1338 
1339  if (urlData.isValid() &&
1340  ((!urlData.expirationDate().isValid()) ||
1341  (urlData.expirationDate().secsTo(now) < 0)))
1342  {
1343  if (QDateTime(urlData.lastModified().toUTC()).secsTo(now) <= 1800)
1344  {
1345  result = urlData.lastModified().toUTC();
1346  }
1347  else
1348  {
1349  QString date = getHeader(urlData, "Date");
1350  if (!date.isNull())
1351  {
1352  QDateTime loadDate =
1353  MythDate::fromString(date, dateFormat);
1354  loadDate.setTimeSpec(Qt::UTC);
1355  if (loadDate.secsTo(now) <= 720)
1356  {
1357  result = urlData.lastModified().toUTC();
1358  }
1359  }
1360  }
1361  }
1362 
1363  if (!result.isValid())
1364  {
1365  MythDownloadInfo *dlInfo = new MythDownloadInfo;
1366  dlInfo->m_url = url;
1367  dlInfo->m_syncMode = true;
1368  // Head request, we only want to inspect the headers
1369  dlInfo->m_requestType = kRequestHead;
1370 
1371  if (downloadNow(dlInfo, false) && dlInfo->m_reply)
1372  {
1373  QVariant lastMod =
1374  dlInfo->m_reply->header(QNetworkRequest::LastModifiedHeader);
1375  if (lastMod.isValid())
1376  result = lastMod.toDateTime().toUTC();
1377  }
1378 
1379  delete dlInfo;
1380  }
1381 
1382  LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1'): Result %2")
1383  .arg(url).arg(result.toString()));
1384 
1385  return result;
1386 }
1387 
1388 
1392 void MythDownloadManager::loadCookieJar(const QString &filename)
1393 {
1394  QMutexLocker locker(&m_cookieLock);
1395 
1396  MythCookieJar *jar = new MythCookieJar;
1397  jar->load(filename);
1398  m_manager->setCookieJar(jar);
1399 }
1400 
1404 void MythDownloadManager::saveCookieJar(const QString &filename)
1405 {
1406  QMutexLocker locker(&m_cookieLock);
1407 
1408  if (!m_manager->cookieJar())
1409  return;
1410 
1411  MythCookieJar *jar = static_cast<MythCookieJar *>(m_manager->cookieJar());
1412  jar->save(filename);
1413 }
1414 
1415 void MythDownloadManager::setCookieJar(QNetworkCookieJar *cookieJar)
1416 {
1417  QMutexLocker locker(&m_cookieLock);
1418  m_manager->setCookieJar(cookieJar);
1419 }
1420 
1424 QNetworkCookieJar *MythDownloadManager::copyCookieJar(void)
1425 {
1426  QMutexLocker locker(&m_cookieLock);
1427 
1428  if (!m_manager->cookieJar())
1429  return NULL;
1430 
1431  MythCookieJar *inJar = static_cast<MythCookieJar *>(m_manager->cookieJar());
1432  MythCookieJar *outJar = new MythCookieJar(*inJar);
1433 
1434  return static_cast<QNetworkCookieJar *>(outJar);
1435 }
1436 
1440 void MythDownloadManager::refreshCookieJar(QNetworkCookieJar *jar)
1441 {
1442  QMutexLocker locker(&m_cookieLock);
1443  if (m_inCookieJar)
1444  delete m_inCookieJar;
1445 
1446  MythCookieJar *inJar = static_cast<MythCookieJar *>(jar);
1447  MythCookieJar *outJar = new MythCookieJar(*inJar);
1448  m_inCookieJar = static_cast<QNetworkCookieJar *>(outJar);
1449 
1450  QMutexLocker locker2(&m_queueWaitLock);
1451  m_queueWaitCond.wakeAll();
1452 }
1453 
1457 {
1458  QMutexLocker locker(&m_cookieLock);
1459 
1460  MythCookieJar *inJar = static_cast<MythCookieJar *>(m_inCookieJar);
1461  MythCookieJar *outJar = new MythCookieJar(*inJar);
1462  m_manager->setCookieJar(static_cast<QNetworkCookieJar *>(outJar));
1463 
1464  delete m_inCookieJar;
1465  m_inCookieJar = NULL;
1466 }
1467 
1468 QString MythDownloadManager::getHeader(const QUrl& url, const QString& header)
1469 {
1470  if (!m_manager || !m_manager->cache())
1471  return QString::null;
1472 
1473  m_infoLock->lock();
1474  QNetworkCacheMetaData metadata = m_manager->cache()->metaData(url);
1475  m_infoLock->unlock();
1476 
1477  return getHeader(metadata, header);
1478 }
1479 
1485 QString MythDownloadManager::getHeader(const QNetworkCacheMetaData &cacheData,
1486  const QString& header)
1487 {
1488  QNetworkCacheMetaData::RawHeaderList headers = cacheData.rawHeaders();
1489  bool found = false;
1490  QNetworkCacheMetaData::RawHeaderList::iterator it = headers.begin();
1491  for (; !found && it != headers.end(); ++it)
1492  {
1493  if (QString((*it).first) == header)
1494  {
1495  found = true;
1496  return QString((*it).second);
1497  }
1498  }
1499 
1500  return QString::null;
1501 }
1502 
1503 
1508 {
1509  const QList<QNetworkCookie> cookieList = old.allCookies();
1510  setAllCookies(cookieList);
1511 }
1512 
1516 {
1517 }
1518 
1522 void MythCookieJar::load(const QString &filename)
1523 {
1524  LOG(VB_GENERAL, LOG_DEBUG, QString("MythCookieJar: loading cookies from: %1").arg(filename));
1525 
1526  QFile f(filename);
1527  if (!f.open(QIODevice::ReadOnly))
1528  {
1529  LOG(VB_GENERAL, LOG_WARNING, QString("MythCookieJar::load() failed to open file for reading: %1").arg(filename));
1530  return;
1531  }
1532 
1533  QList<QNetworkCookie> cookieList;
1534  QTextStream stream(&f);
1535  while (!stream.atEnd())
1536  {
1537  QString cookie = stream.readLine();
1538  cookieList << QNetworkCookie::parseCookies(cookie.toLocal8Bit());
1539  }
1540 
1541  setAllCookies(cookieList);
1542 }
1543 
1547 void MythCookieJar::save(const QString &filename)
1548 {
1549  LOG(VB_GENERAL, LOG_DEBUG, QString("MythCookieJar: saving cookies to: %1").arg(filename));
1550 
1551  QFile f(filename);
1552  if (!f.open(QIODevice::WriteOnly))
1553  {
1554  LOG(VB_GENERAL, LOG_ERR, QString("MythCookieJar::save() failed to open file for writing: %1").arg(filename));
1555  return;
1556  }
1557 
1558  QList<QNetworkCookie> cookieList = allCookies();
1559  QTextStream stream(&f);
1560 
1561  for (QList<QNetworkCookie>::iterator it = cookieList.begin();
1562  it != cookieList.end(); ++it)
1563  {
1564  stream << (*it).toRawForm() << endl;
1565  }
1566 }
1567 
1568 
1569 /* vim: set expandtab tabstop=4 shiftwidth=4: */
1570