00001
00002 #include <QCoreApplication>
00003 #include <QRunnable>
00004 #include <QString>
00005 #include <QByteArray>
00006 #include <QFile>
00007 #include <QDir>
00008 #include <QNetworkCookieJar>
00009 #include <QAuthenticator>
00010 #include <QTextStream>
00011 #include <QNetworkProxy>
00012 #include <QMutexLocker>
00013
00014 #include "stdlib.h"
00015
00016
00017 #include "compat.h"
00018 #include "mythcorecontext.h"
00019 #include "mythcoreutil.h"
00020 #include "mthreadpool.h"
00021 #include "mythdirs.h"
00022 #include "mythevent.h"
00023 #include "mythversion.h"
00024 #include "remotefile.h"
00025
00026 #include "mythdownloadmanager.h"
00027 #include "mythlogging.h"
00028 #include <QUrl>
00029
00030 using namespace std;
00031
00032 #define LOC QString("DownloadManager: ")
00033 #define CACHE_REDIRECTION_LIMIT 10
00034
00035 MythDownloadManager *downloadManager = NULL;
00036 QMutex dmCreateLock;
00037
00041 class MythDownloadInfo
00042 {
00043 public:
00044 MythDownloadInfo() :
00045 m_request(NULL), m_reply(NULL), m_data(NULL),
00046 m_caller(NULL), m_requestType(kRequestGet),
00047 m_reload(false), m_preferCache(false), m_syncMode(false),
00048 m_processReply(true), m_done(false), m_bytesReceived(0),
00049 m_bytesTotal(0), m_lastStat(QDateTime::currentDateTime()),
00050 m_authCallback(NULL), m_authArg(NULL),
00051 m_header(NULL), m_headerVal(NULL),
00052 m_errorCode(QNetworkReply::NoError)
00053 {
00054 qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
00055 }
00056
00057 ~MythDownloadInfo()
00058 {
00059 if (m_request)
00060 delete m_request;
00061 if (m_reply && m_processReply)
00062 m_reply->deleteLater();
00063 }
00064
00065 void detach(void)
00066 {
00067 m_url.detach();
00068 m_outFile.detach();
00069 }
00070
00071 QString m_url;
00072 QUrl m_redirectedTo;
00073 QNetworkRequest *m_request;
00074 QNetworkReply *m_reply;
00075 QString m_outFile;
00076 QByteArray *m_data;
00077 QByteArray m_privData;
00078 QObject *m_caller;
00079 MRequestType m_requestType;
00080 bool m_reload;
00081 bool m_preferCache;
00082 bool m_syncMode;
00083 bool m_processReply;
00084 bool m_done;
00085 qint64 m_bytesReceived;
00086 qint64 m_bytesTotal;
00087 QDateTime m_lastStat;
00088 AuthCallback m_authCallback;
00089 void *m_authArg;
00090 const QByteArray *m_header;
00091 const QByteArray *m_headerVal;
00092
00093 QNetworkReply::NetworkError m_errorCode;
00094 };
00095
00096
00101 class MythCookieJar : public QNetworkCookieJar
00102 {
00103 public:
00104 MythCookieJar();
00105 MythCookieJar(MythCookieJar &old);
00106 void load(const QString &filename);
00107 void save(const QString &filename);
00108 };
00109
00113 class RemoteFileDownloadThread : public QRunnable
00114 {
00115 public:
00116 RemoteFileDownloadThread(MythDownloadManager *parent,
00117 MythDownloadInfo *dlInfo) :
00118 m_parent(parent),
00119 m_dlInfo(dlInfo)
00120 {
00121 m_dlInfo->detach();
00122 }
00123
00124 void run()
00125 {
00126 bool ok = false;
00127
00128 RemoteFile *rf = new RemoteFile(m_dlInfo->m_url, false, false, 0);
00129 ok = rf->SaveAs(m_dlInfo->m_privData);
00130 delete rf;
00131
00132 if (!ok)
00133 m_dlInfo->m_errorCode = QNetworkReply::UnknownNetworkError;
00134
00135 m_dlInfo->m_bytesReceived = m_dlInfo->m_privData.size();
00136 m_dlInfo->m_bytesTotal = m_dlInfo->m_bytesReceived;
00137
00138 m_parent->downloadFinished(m_dlInfo);
00139 }
00140
00141 private:
00142 MythDownloadManager *m_parent;
00143 MythDownloadInfo *m_dlInfo;
00144 };
00145
00148 void ShutdownMythDownloadManager(void)
00149 {
00150 if (downloadManager)
00151 {
00152 delete downloadManager;
00153 downloadManager = NULL;
00154 }
00155 }
00156
00160 MythDownloadManager *GetMythDownloadManager(void)
00161 {
00162 if (downloadManager)
00163 return downloadManager;
00164
00165 QMutexLocker locker(&dmCreateLock);
00166
00167
00168
00169 if (downloadManager)
00170 return downloadManager;
00171
00172 MythDownloadManager *tmpDLM = new MythDownloadManager();
00173 tmpDLM->start();
00174 while (!tmpDLM->getQueueThread())
00175 usleep(10000);
00176
00177 tmpDLM->moveToThread(tmpDLM->getQueueThread());
00178 tmpDLM->setRunThread();
00179
00180 while (!tmpDLM->isRunning())
00181 usleep(10000);
00182
00183 downloadManager = tmpDLM;
00184
00185 atexit(ShutdownMythDownloadManager);
00186
00187 return downloadManager;
00188 }
00189
00193 MythDownloadManager::MythDownloadManager() :
00194 MThread("DownloadManager"),
00195 m_manager(NULL),
00196 m_diskCache(NULL),
00197 m_proxy(NULL),
00198 m_infoLock(new QMutex(QMutex::Recursive)),
00199 m_queueThread(NULL),
00200 m_runThread(false),
00201 m_isRunning(false),
00202 m_inCookieJar(NULL)
00203 {
00204 }
00205
00208 MythDownloadManager::~MythDownloadManager()
00209 {
00210 m_runThread = false;
00211 m_queueWaitCond.wakeAll();
00212
00213 wait();
00214
00215 delete m_infoLock;
00216
00217 if (m_inCookieJar)
00218 delete m_inCookieJar;
00219 }
00220
00224 void MythDownloadManager::run(void)
00225 {
00226 RunProlog();
00227
00228 bool downloading = false;
00229 bool itemsInQueue = false;
00230 bool waitAnyway = false;
00231
00232 m_queueThread = QThread::currentThread();
00233
00234 while (!m_runThread)
00235 usleep(50000);
00236
00237 m_manager = new QNetworkAccessManager(this);
00238 m_diskCache = new QNetworkDiskCache(this);
00239 m_proxy = new QNetworkProxy();
00240 m_diskCache->setCacheDirectory(GetConfDir() + "/Cache-" +
00241 QCoreApplication::applicationName() + "-" +
00242 gCoreContext->GetHostName());
00243 m_manager->setCache(m_diskCache);
00244
00245
00246
00247 m_manager->setProxy(*m_proxy);
00248
00249
00250
00251 m_manager->cookieJar()->setParent(NULL);
00252
00253 QObject::connect(m_manager, SIGNAL(finished(QNetworkReply*)), this,
00254 SLOT(downloadFinished(QNetworkReply*)));
00255
00256 m_isRunning = true;
00257 while (m_runThread)
00258 {
00259 if (m_inCookieJar)
00260 {
00261 LOG(VB_GENERAL, LOG_DEBUG, "Updating DLManager's Cookie Jar");
00262 updateCookieJar();
00263 }
00264 m_infoLock->lock();
00265 downloading = !m_downloadInfos.isEmpty();
00266 itemsInQueue = !m_downloadQueue.isEmpty();
00267 m_infoLock->unlock();
00268
00269 if (downloading)
00270 QCoreApplication::processEvents();
00271
00272 if (!itemsInQueue || waitAnyway)
00273 {
00274 waitAnyway = false;
00275 m_queueWaitLock.lock();
00276
00277 if (downloading)
00278 m_queueWaitCond.wait(&m_queueWaitLock, 200);
00279 else
00280 m_queueWaitCond.wait(&m_queueWaitLock);
00281
00282 m_queueWaitLock.unlock();
00283 }
00284
00285 m_infoLock->lock();
00286 if (!m_downloadQueue.isEmpty())
00287 {
00288 MythDownloadInfo *dlInfo = m_downloadQueue.front();
00289
00290 m_downloadQueue.pop_front();
00291
00292 if (!dlInfo)
00293 continue;
00294
00295 QUrl qurl(dlInfo->m_url);
00296 if (m_downloadInfos.contains(qurl.toString()))
00297 {
00298
00299
00300
00301 if (m_downloadQueue.isEmpty())
00302 waitAnyway = true;
00303 m_downloadQueue.push_back(dlInfo);
00304 m_infoLock->unlock();
00305 continue;
00306 }
00307
00308 if (dlInfo->m_url.startsWith("myth://"))
00309 downloadRemoteFile(dlInfo);
00310 else
00311 {
00312 QMutexLocker cLock(&m_cookieLock);
00313 downloadQNetworkRequest(dlInfo);
00314 }
00315
00316 m_downloadInfos[qurl.toString()] = dlInfo;
00317 }
00318 m_infoLock->unlock();
00319 }
00320 m_isRunning = false;
00321
00322 RunEpilog();
00323 }
00324
00335 void MythDownloadManager::queueItem(const QString &url, QNetworkRequest *req,
00336 const QString &dest, QByteArray *data,
00337 QObject *caller, const MRequestType reqType,
00338 const bool reload)
00339 {
00340 MythDownloadInfo *dlInfo = new MythDownloadInfo;
00341
00342 dlInfo->m_url = url;
00343 dlInfo->m_request = req;
00344 dlInfo->m_outFile = dest;
00345 dlInfo->m_data = data;
00346 dlInfo->m_caller = caller;
00347 dlInfo->m_requestType = reqType;
00348 dlInfo->m_reload = reload;
00349
00350 dlInfo->detach();
00351
00352 QMutexLocker locker(m_infoLock);
00353 m_downloadQueue.push_back(dlInfo);
00354 m_queueWaitCond.wakeAll();
00355 }
00356
00370 bool MythDownloadManager::processItem(const QString &url, QNetworkRequest *req,
00371 const QString &dest, QByteArray *data,
00372 const MRequestType reqType,
00373 const bool reload,
00374 AuthCallback authCallback, void *authArg,
00375 const QByteArray *header,
00376 const QByteArray *headerVal)
00377 {
00378 MythDownloadInfo *dlInfo = new MythDownloadInfo;
00379
00380 dlInfo->m_url = url;
00381 dlInfo->m_request = req;
00382 dlInfo->m_outFile = dest;
00383 dlInfo->m_data = data;
00384 dlInfo->m_requestType = reqType;
00385 dlInfo->m_reload = reload;
00386 dlInfo->m_syncMode = true;
00387 dlInfo->m_authCallback = authCallback;
00388 dlInfo->m_authArg = authArg;
00389 dlInfo->m_header = header;
00390 dlInfo->m_headerVal = headerVal;
00391
00392 return downloadNow(dlInfo);
00393 }
00394
00398 void MythDownloadManager::preCache(const QString &url)
00399 {
00400 LOG(VB_FILE, LOG_DEBUG, LOC + QString("preCache('%1')").arg(url));
00401 queueItem(url, NULL, QString(), NULL, NULL);
00402 }
00403
00410 void MythDownloadManager::queueDownload(const QString &url,
00411 const QString &dest,
00412 QObject *caller,
00413 const bool reload)
00414 {
00415 LOG(VB_FILE, LOG_DEBUG, LOC + QString("queueDownload('%1', '%2', %3)")
00416 .arg(url).arg(dest).arg((long long)caller));
00417
00418 queueItem(url, NULL, dest, NULL, caller, kRequestGet, reload);
00419 }
00420
00426 void MythDownloadManager::queueDownload(QNetworkRequest *req,
00427 QByteArray *data,
00428 QObject *caller)
00429 {
00430 LOG(VB_FILE, LOG_DEBUG, LOC + QString("queueDownload('%1', '%2', %3)")
00431 .arg(req->url().toString()).arg((long long)data)
00432 .arg((long long)caller));
00433
00434 queueItem(req->url().toString(), req, QString(), data, caller);
00435 }
00436
00443 bool MythDownloadManager::download(const QString &url, const QString &dest,
00444 const bool reload)
00445 {
00446 return processItem(url, NULL, dest, NULL, kRequestGet, reload);
00447 }
00448
00455 bool MythDownloadManager::download(const QString &url, QByteArray *data,
00456 const bool reload)
00457 {
00458 return processItem(url, NULL, QString(), data, kRequestGet, reload);
00459 }
00460
00467 QNetworkReply *MythDownloadManager::download(const QString &url,
00468 const bool reload)
00469 {
00470 MythDownloadInfo *dlInfo = new MythDownloadInfo;
00471
00472 dlInfo->m_url = url;
00473 dlInfo->m_reload = reload;
00474 dlInfo->m_syncMode = true;
00475 dlInfo->m_processReply = false;
00476
00477 bool ok = downloadNow(dlInfo, false);
00478
00479 QNetworkReply *reply = dlInfo->m_reply;
00480
00481 if (reply)
00482 dlInfo->m_reply = NULL;
00483
00484 delete dlInfo;
00485 dlInfo = NULL;
00486
00487 if (ok && reply)
00488 return reply;
00489
00490 return NULL;
00491 }
00492
00498 bool MythDownloadManager::download(QNetworkRequest *req, QByteArray *data)
00499 {
00500 LOG(VB_FILE, LOG_DEBUG, LOC + QString("download('%1', '%2')")
00501 .arg(req->url().toString()).arg((long long)data));
00502 return processItem(req->url().toString(), req, QString(), data);
00503 }
00504
00515 bool MythDownloadManager::downloadAuth(const QString &url, const QString &dest,
00516 const bool reload, AuthCallback authCallback, void *authArg,
00517 const QByteArray *header, const QByteArray *headerVal)
00518 {
00519 return processItem(url, NULL, dest, NULL, kRequestGet, reload, authCallback,
00520 authArg, header, headerVal);
00521 }
00522
00523
00529 void MythDownloadManager::queuePost(const QString &url,
00530 QByteArray *data,
00531 QObject *caller)
00532 {
00533 LOG(VB_FILE, LOG_DEBUG, LOC + QString("queuePost('%1', '%2')")
00534 .arg(url).arg((long long)data));
00535
00536 if (!data)
00537 {
00538 LOG(VB_GENERAL, LOG_ERR, LOC + "queuePost(), data is NULL!");
00539 return;
00540 }
00541
00542 queueItem(url, NULL, QString(), data, caller, kRequestPost);
00543 }
00544
00550 void MythDownloadManager::queuePost(QNetworkRequest *req,
00551 QByteArray *data,
00552 QObject *caller)
00553 {
00554 LOG(VB_FILE, LOG_DEBUG, LOC + QString("queuePost('%1', '%2')")
00555 .arg(req->url().toString()).arg((long long)data));
00556
00557 if (!data)
00558 {
00559 LOG(VB_GENERAL, LOG_ERR, LOC + "queuePost(), data is NULL!");
00560 return;
00561 }
00562
00563 queueItem(req->url().toString(), req, QString(), data, caller,
00564 kRequestPost);
00565 }
00566
00572 bool MythDownloadManager::post(const QString &url, QByteArray *data)
00573 {
00574 LOG(VB_FILE, LOG_DEBUG, LOC + QString("post('%1', '%2')")
00575 .arg(url).arg((long long)data));
00576
00577 if (!data)
00578 {
00579 LOG(VB_GENERAL, LOG_ERR, LOC + "post(), data is NULL!");
00580 return false;
00581 }
00582
00583 return processItem(url, NULL, QString(), data, kRequestPost);
00584 }
00585
00591 bool MythDownloadManager::post(QNetworkRequest *req, QByteArray *data)
00592 {
00593 LOG(VB_FILE, LOG_DEBUG, LOC + QString("post('%1', '%2')")
00594 .arg(req->url().toString()).arg((long long)data));
00595
00596 if (!data)
00597 {
00598 LOG(VB_GENERAL, LOG_ERR, LOC + "post(), data is NULL!");
00599 return false;
00600 }
00601
00602 return processItem(req->url().toString(), req, QString(), data,
00603 kRequestPost);
00604 }
00605
00615 bool MythDownloadManager::postAuth(const QString &url, QByteArray *data,
00616 AuthCallback authCallback, void *authArg,
00617 const QByteArray *header,
00618 const QByteArray *headerVal)
00619 {
00620 LOG(VB_FILE, LOG_DEBUG, LOC + QString("postAuth('%1', '%2')")
00621 .arg(url).arg((long long)data));
00622
00623 if (!data)
00624 {
00625 LOG(VB_GENERAL, LOG_ERR, LOC + "postAuth(), data is NULL!");
00626 return false;
00627 }
00628
00629 return processItem(url, NULL, NULL, data, kRequestPost, false, authCallback,
00630 authArg, header, headerVal);
00631 }
00632
00636 void MythDownloadManager::downloadRemoteFile(MythDownloadInfo *dlInfo)
00637 {
00638 RemoteFileDownloadThread *dlThread =
00639 new RemoteFileDownloadThread(this, dlInfo);
00640 MThreadPool::globalInstance()->start(dlThread, "RemoteFileDownload");
00641 }
00642
00646 void MythDownloadManager::downloadQNetworkRequest(MythDownloadInfo *dlInfo)
00647 {
00648 if (!dlInfo)
00649 return;
00650
00651 static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
00652 QUrl qurl(dlInfo->m_url);
00653 QNetworkRequest request;
00654
00655 if (dlInfo->m_request)
00656 {
00657 request = *dlInfo->m_request;
00658 delete dlInfo->m_request;
00659 dlInfo->m_request = NULL;
00660 }
00661 else
00662 request.setUrl(qurl);
00663
00664 if (!dlInfo->m_reload)
00665 {
00666
00667
00668 QDateTime now = QDateTime::currentDateTime();
00669
00670
00671 QString redirectLoc;
00672 int limit = 0;
00673 while (!(redirectLoc = getHeader(qurl, "Location")).isNull())
00674 {
00675 if (limit == CACHE_REDIRECTION_LIMIT)
00676 {
00677 LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
00678 "reached for %1")
00679 .arg(qurl.toString()));
00680 return;
00681 }
00682 qurl.setUrl(redirectLoc);
00683 limit++;
00684 }
00685
00686 LOG(VB_NETWORK, LOG_DEBUG, QString("Checking cache for %1")
00687 .arg(qurl.toString()));
00688
00689 m_infoLock->lock();
00690 QNetworkCacheMetaData urlData = m_manager->cache()->metaData(qurl);
00691 m_infoLock->unlock();
00692 if ((urlData.isValid()) &&
00693 ((!urlData.expirationDate().isValid()) ||
00694 (urlData.expirationDate().secsTo(now) < 10)))
00695 {
00696 QString dateString = getHeader(urlData, "Date");
00697
00698 if (!dateString.isNull())
00699 {
00700 QDateTime loadDate = QDateTime::fromString(dateString,
00701 dateFormat);
00702 loadDate.setTimeSpec(Qt::UTC);
00703 if (loadDate.secsTo(now) <= 720)
00704 {
00705 dlInfo->m_preferCache = true;
00706 LOG(VB_NETWORK, LOG_DEBUG, QString("Prefering cache for %1")
00707 .arg(qurl.toString()));
00708 }
00709 }
00710 }
00711 }
00712
00713 if (dlInfo->m_preferCache)
00714 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
00715 QNetworkRequest::PreferCache);
00716
00717 request.setRawHeader("User-Agent",
00718 "MythTV v" MYTH_BINARY_VERSION " MythDownloadManager");
00719
00720 if (dlInfo->m_header && dlInfo->m_headerVal &&
00721 !dlInfo->m_header->isEmpty() && !dlInfo->m_headerVal->isEmpty())
00722 {
00723 request.setRawHeader(*(dlInfo->m_header), *(dlInfo->m_headerVal));
00724 }
00725
00726 switch (dlInfo->m_requestType)
00727 {
00728 case kRequestPost :
00729 dlInfo->m_reply = m_manager->post(request, *dlInfo->m_data);
00730 break;
00731 case kRequestHead :
00732 dlInfo->m_reply = m_manager->head(request);
00733 break;
00734 case kRequestGet :
00735 default:
00736 dlInfo->m_reply = m_manager->get(request);
00737 break;
00738 }
00739
00740 m_downloadReplies[dlInfo->m_reply] = dlInfo;
00741
00742 if (dlInfo->m_authCallback)
00743 {
00744 connect(m_manager, SIGNAL(authenticationRequired(QNetworkReply *,
00745 QAuthenticator *)),
00746 this, SLOT(authCallback(QNetworkReply *, QAuthenticator *)));
00747 }
00748
00749 connect(dlInfo->m_reply, SIGNAL(error(QNetworkReply::NetworkError)), this,
00750 SLOT(downloadError(QNetworkReply::NetworkError)));
00751 connect(dlInfo->m_reply, SIGNAL(downloadProgress(qint64, qint64)),
00752 this, SLOT(downloadProgress(qint64, qint64)));
00753 }
00754
00759 void MythDownloadManager::authCallback(QNetworkReply *reply,
00760 QAuthenticator *authenticator)
00761 {
00762 if (!reply)
00763 return;
00764
00765 MythDownloadInfo *dlInfo = m_downloadReplies[reply];
00766
00767 if (!dlInfo)
00768 return;
00769
00770 if (dlInfo->m_authCallback)
00771 {
00772 LOG(VB_FILE, LOG_DEBUG, "Calling auth callback");
00773 dlInfo->m_authCallback(reply, authenticator, dlInfo->m_authArg);
00774 }
00775 }
00776
00783 bool MythDownloadManager::downloadNow(MythDownloadInfo *dlInfo, bool deleteInfo)
00784 {
00785 if (!dlInfo)
00786 return false;
00787
00788 dlInfo->m_syncMode = true;
00789
00790 m_infoLock->lock();
00791 m_downloadQueue.push_back(dlInfo);
00792 m_infoLock->unlock();
00793 m_queueWaitCond.wakeAll();
00794
00795
00796
00797
00798 QDateTime startedAt = QDateTime::currentDateTime();
00799 m_infoLock->lock();
00800 while ((!dlInfo->m_done) &&
00801 (dlInfo->m_errorCode == QNetworkReply::NoError) &&
00802 (((!dlInfo->m_url.startsWith("myth://")) &&
00803 (dlInfo->m_lastStat.secsTo(QDateTime::currentDateTime()) < 10)) ||
00804 ((dlInfo->m_url.startsWith("myth://")) &&
00805 (startedAt.secsTo(QDateTime::currentDateTime()) < 20))))
00806 {
00807 m_infoLock->unlock();
00808 m_queueWaitLock.lock();
00809 m_queueWaitCond.wait(&m_queueWaitLock, 200);
00810 m_queueWaitLock.unlock();
00811 m_infoLock->lock();
00812 }
00813
00814 bool success =
00815 dlInfo->m_done && (dlInfo->m_errorCode == QNetworkReply::NoError);
00816
00817 if (!dlInfo->m_done)
00818 {
00819 dlInfo->m_data = NULL;
00820 dlInfo->m_syncMode = false;
00821 if ((dlInfo->m_reply) &&
00822 (dlInfo->m_errorCode == QNetworkReply::NoError))
00823 {
00824 LOG(VB_FILE, LOG_DEBUG,
00825 LOC + QString("Aborting download - lack of data transfer"));
00826 dlInfo->m_reply->abort();
00827 }
00828 }
00829 else if (deleteInfo)
00830 {
00831 delete dlInfo;
00832 dlInfo = NULL;
00833 }
00834
00835 m_infoLock->unlock();
00836
00837 return success;
00838 }
00839
00843 void MythDownloadManager::cancelDownload(const QString &url)
00844 {
00845 QMutexLocker locker(m_infoLock);
00846 MythDownloadInfo *dlInfo;
00847
00848 QMutableListIterator<MythDownloadInfo*> lit(m_downloadQueue);
00849 while (lit.hasNext())
00850 {
00851 lit.next();
00852 dlInfo = lit.value();
00853 if (dlInfo->m_url == url)
00854 {
00855
00856 if (dlInfo->m_reply)
00857 {
00858 LOG(VB_FILE, LOG_DEBUG,
00859 LOC + QString("Aborting download - user request"));
00860 dlInfo->m_reply->abort();
00861 }
00862 lit.remove();
00863 delete dlInfo;
00864 dlInfo = NULL;
00865 }
00866 }
00867
00868 if (m_downloadInfos.contains(url))
00869 {
00870 dlInfo = m_downloadInfos[url];
00871 if (dlInfo->m_reply)
00872 {
00873 LOG(VB_FILE, LOG_DEBUG,
00874 LOC + QString("Aborting download - user request"));
00875 m_downloadReplies.remove(dlInfo->m_reply);
00876 dlInfo->m_reply->abort();
00877 }
00878 m_downloadInfos.remove(url);
00879 delete dlInfo;
00880 dlInfo = NULL;
00881 }
00882 }
00883
00888 void MythDownloadManager::removeListener(QObject *caller)
00889 {
00890 QMutexLocker locker(m_infoLock);
00891 MythDownloadInfo *dlInfo;
00892
00893 QList <MythDownloadInfo*>::iterator lit = m_downloadQueue.begin();
00894 for (; lit != m_downloadQueue.end(); ++lit)
00895 {
00896 dlInfo = *lit;
00897 if (dlInfo->m_caller == caller)
00898 {
00899 dlInfo->m_caller = NULL;
00900 dlInfo->m_outFile = QString();
00901 dlInfo->m_data = NULL;
00902 }
00903 }
00904
00905 QMap <QString, MythDownloadInfo*>::iterator mit = m_downloadInfos.begin();
00906 for (; mit != m_downloadInfos.end(); ++mit)
00907 {
00908 dlInfo = mit.value();
00909 if (dlInfo->m_caller == caller)
00910 {
00911 dlInfo->m_caller = NULL;
00912 dlInfo->m_outFile = QString();
00913 dlInfo->m_data = NULL;
00914 }
00915 }
00916 }
00917
00921 void MythDownloadManager::downloadError(QNetworkReply::NetworkError errorCode)
00922 {
00923 QNetworkReply *reply = (QNetworkReply*)sender();
00924
00925 LOG(VB_FILE, LOG_DEBUG, LOC + QString("downloadError %1 ")
00926 .arg(errorCode) + reply->errorString() );
00927
00928 QMutexLocker locker(m_infoLock);
00929 if (!m_downloadReplies.contains(reply))
00930 {
00931 reply->deleteLater();
00932 return;
00933 }
00934
00935 MythDownloadInfo *dlInfo = m_downloadReplies[reply];
00936
00937 if (!dlInfo)
00938 return;
00939
00940 dlInfo->m_errorCode = errorCode;
00941 }
00942
00948 QUrl MythDownloadManager::redirectUrl(const QUrl& possibleRedirectUrl,
00949 const QUrl& oldRedirectUrl) const
00950 {
00951 LOG(VB_FILE, LOG_DEBUG, LOC + QString("redirectUrl()"));
00952 QUrl redirectUrl;
00953
00954 if(!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != oldRedirectUrl)
00955 redirectUrl = possibleRedirectUrl;
00956
00957 return redirectUrl;
00958 }
00959
00963 void MythDownloadManager::downloadFinished(QNetworkReply* reply)
00964 {
00965 LOG(VB_FILE, LOG_DEBUG, LOC + QString("downloadFinished(%1)")
00966 .arg((long long)reply));
00967
00968 QMutexLocker locker(m_infoLock);
00969 if (!m_downloadReplies.contains(reply))
00970 {
00971 reply->deleteLater();
00972 return;
00973 }
00974
00975 MythDownloadInfo *dlInfo = m_downloadReplies[reply];
00976
00977 if (!dlInfo || !dlInfo->m_reply)
00978 return;
00979
00980 downloadFinished(dlInfo);
00981 }
00982
00986 void MythDownloadManager::downloadFinished(MythDownloadInfo *dlInfo)
00987 {
00988 if (!dlInfo)
00989 return;
00990
00991 static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
00992 QNetworkReply *reply = dlInfo->m_reply;
00993
00994 if (reply)
00995 {
00996 QUrl possibleRedirectUrl =
00997 reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
00998
00999 dlInfo->m_redirectedTo =
01000 redirectUrl(possibleRedirectUrl, dlInfo->m_redirectedTo);
01001 }
01002
01003 if(!dlInfo->m_redirectedTo.isEmpty())
01004 {
01005 LOG(VB_FILE, LOG_DEBUG, LOC +
01006 QString("downloadFinished(%1): Redirect: %2 -> %3")
01007 .arg((long long)dlInfo)
01008 .arg(reply->url().toString())
01009 .arg(dlInfo->m_redirectedTo.toString()));
01010
01011 QNetworkRequest request(dlInfo->m_redirectedTo);
01012 if (dlInfo->m_preferCache)
01013 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
01014 QNetworkRequest::PreferCache);
01015 request.setRawHeader("User-Agent",
01016 "MythDownloadManager v" MYTH_BINARY_VERSION);
01017
01018 switch (dlInfo->m_requestType)
01019 {
01020 case kRequestPost :
01021 dlInfo->m_reply = m_manager->post(request, *dlInfo->m_data);
01022 break;
01023 case kRequestHead :
01024 dlInfo->m_reply = m_manager->head(request);
01025 break;
01026 case kRequestGet :
01027 default:
01028 dlInfo->m_reply = m_manager->get(request);
01029 break;
01030 }
01031
01032 m_downloadReplies[dlInfo->m_reply] = dlInfo;
01033
01034 connect(dlInfo->m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
01035 this, SLOT(downloadError(QNetworkReply::NetworkError)));
01036 connect(dlInfo->m_reply, SIGNAL(downloadProgress(qint64, qint64)),
01037 this, SLOT(downloadProgress(qint64, qint64)));
01038
01039 m_downloadReplies.remove(reply);
01040 reply->deleteLater();
01041 }
01042 else
01043 {
01044 LOG(VB_FILE, LOG_DEBUG, QString("downloadFinished(%1): COMPLETE: %2")
01045 .arg((long long)dlInfo).arg(dlInfo->m_url));
01046
01047
01048
01049 QUrl fileUrl = dlInfo->m_url;
01050 QString redirectLoc;
01051 int limit = 0;
01052 while (!(redirectLoc = getHeader(fileUrl, "Location")).isNull())
01053 {
01054 if (limit == CACHE_REDIRECTION_LIMIT)
01055 {
01056 LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
01057 "reached for %1")
01058 .arg(fileUrl.toString()));
01059 return;
01060 }
01061 fileUrl.setUrl(redirectLoc);
01062 limit++;
01063 }
01064
01065 m_infoLock->lock();
01066 QNetworkCacheMetaData urlData = m_manager->cache()->metaData(fileUrl);
01067 m_infoLock->unlock();
01068 if (getHeader(urlData, "Date").isNull())
01069 {
01070 QNetworkCacheMetaData::RawHeaderList headers = urlData.rawHeaders();
01071 QNetworkCacheMetaData::RawHeader newheader;
01072 QDateTime now = QDateTime::currentDateTime().toUTC();
01073 newheader = QNetworkCacheMetaData::RawHeader("Date",
01074 now.toString(dateFormat).toAscii());
01075 headers.append(newheader);
01076 urlData.setRawHeaders(headers);
01077 m_infoLock->lock();
01078 m_manager->cache()->updateMetaData(urlData);
01079 m_infoLock->unlock();
01080 }
01081
01082
01083 dlInfo->m_redirectedTo.clear();
01084
01085 int dataSize = -1;
01086
01087
01088
01089 if (reply && dlInfo->m_processReply)
01090 {
01091 bool append = (!dlInfo->m_syncMode && dlInfo->m_caller);
01092 QByteArray data = reply->readAll();
01093 dataSize = data.size();
01094
01095 if (append)
01096 dlInfo->m_bytesReceived += dataSize;
01097 else
01098 dlInfo->m_bytesReceived = dataSize;
01099
01100 dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
01101
01102 if (dlInfo->m_data)
01103 {
01104 if (append)
01105 dlInfo->m_data->append(data);
01106 else
01107 *dlInfo->m_data = data;
01108 }
01109 else if (!dlInfo->m_outFile.isEmpty())
01110 {
01111 saveFile(dlInfo->m_outFile, data, append);
01112 }
01113 }
01114 else if (!reply)
01115 {
01116 if (dlInfo->m_data)
01117 {
01118 (*dlInfo->m_data) = dlInfo->m_privData;
01119 }
01120 else if (!dlInfo->m_outFile.isEmpty())
01121 {
01122 saveFile(dlInfo->m_outFile, dlInfo->m_privData);
01123 }
01124 dlInfo->m_bytesReceived += dataSize;
01125 dlInfo->m_bytesTotal = dlInfo->m_bytesReceived;
01126 }
01127
01128
01129
01130 m_downloadInfos.remove(dlInfo->m_url);
01131 if (reply)
01132 m_downloadReplies.remove(reply);
01133
01134 dlInfo->m_done = true;
01135
01136 if (!dlInfo->m_syncMode)
01137 {
01138 if (dlInfo->m_caller)
01139 {
01140 LOG(VB_FILE, LOG_DEBUG, QString("downloadFinished(%1): "
01141 "COMPLETE: %2, sending event to caller")
01142 .arg((long long)dlInfo).arg(dlInfo->m_url));
01143
01144 QStringList args;
01145 args << dlInfo->m_url;
01146 args << dlInfo->m_outFile;
01147 args << QString::number(dlInfo->m_bytesTotal);
01148
01149 args << (reply ? reply->errorString() : QString());
01150 args << QString::number((int)(reply ? reply->error() :
01151 dlInfo->m_errorCode));
01152
01153 QCoreApplication::postEvent(dlInfo->m_caller,
01154 new MythEvent("DOWNLOAD_FILE FINISHED", args));
01155 }
01156
01157 delete dlInfo;
01158 dlInfo = NULL;
01159 }
01160
01161 m_queueWaitCond.wakeAll();
01162 }
01163 }
01164
01170 void MythDownloadManager::downloadProgress(qint64 bytesReceived,
01171 qint64 bytesTotal)
01172 {
01173 QNetworkReply *reply = (QNetworkReply*)sender();
01174
01175 LOG(VB_FILE, LOG_DEBUG, LOC +
01176 QString("downloadProgress(%1, %2) (for reply %3)")
01177 .arg(bytesReceived).arg(bytesTotal).arg((long long)reply));
01178
01179 QMutexLocker locker(m_infoLock);
01180 if (!m_downloadReplies.contains(reply))
01181 return;
01182
01183 MythDownloadInfo *dlInfo = m_downloadReplies[reply];
01184
01185 if (!dlInfo)
01186 return;
01187
01188 dlInfo->m_lastStat = QDateTime::currentDateTime();
01189
01190 LOG(VB_FILE, LOG_DEBUG, LOC +
01191 QString("downloadProgress: %1 to %2 is at %3 of %4 bytes downloaded")
01192 .arg(dlInfo->m_url).arg(dlInfo->m_outFile)
01193 .arg(bytesReceived).arg(bytesTotal));
01194
01195 if (!dlInfo->m_syncMode && dlInfo->m_caller)
01196 {
01197 LOG(VB_FILE, LOG_DEBUG, QString("downloadProgress(%1): "
01198 "sending event to caller")
01199 .arg(reply->url().toString()));
01200
01201 bool appendToFile = (dlInfo->m_bytesReceived != 0);
01202 QByteArray data = reply->readAll();
01203 if (!dlInfo->m_outFile.isEmpty())
01204 saveFile(dlInfo->m_outFile, data, appendToFile);
01205
01206 if (dlInfo->m_data)
01207 dlInfo->m_data->append(data);
01208
01209 dlInfo->m_bytesReceived = bytesReceived;
01210 dlInfo->m_bytesTotal = bytesTotal;
01211
01212 QStringList args;
01213 args << dlInfo->m_url;
01214 args << dlInfo->m_outFile;
01215 args << QString::number(bytesReceived);
01216 args << QString::number(bytesTotal);
01217
01218 QCoreApplication::postEvent(dlInfo->m_caller,
01219 new MythEvent("DOWNLOAD_FILE UPDATE", args));
01220 }
01221 }
01222
01230 bool MythDownloadManager::saveFile(const QString &outFile,
01231 const QByteArray &data,
01232 const bool append)
01233 {
01234 if (outFile.isEmpty() || !data.size())
01235 return false;
01236
01237 QFile file(outFile);
01238 QFileInfo fileInfo(outFile);
01239 QDir qdir(fileInfo.absolutePath());
01240
01241 if (!qdir.exists() && !qdir.mkpath(fileInfo.absolutePath()))
01242 {
01243 LOG(VB_GENERAL, LOG_ERR, QString("Failed to create: '%1'")
01244 .arg(fileInfo.absolutePath()));
01245 return false;
01246 }
01247
01248 QIODevice::OpenMode mode = QIODevice::Unbuffered|QIODevice::WriteOnly;
01249 if (append)
01250 mode |= QIODevice::Append;
01251
01252 if (!file.open(mode))
01253 {
01254 LOG(VB_GENERAL, LOG_ERR, QString("Failed to open: '%1'") .arg(outFile));
01255 return false;
01256 }
01257
01258 off_t offset = 0;
01259 size_t remaining = data.size();
01260 uint failure_cnt = 0;
01261 while ((remaining > 0) && (failure_cnt < 5))
01262 {
01263 ssize_t written = file.write(data.data() + offset, remaining);
01264 if (written < 0)
01265 {
01266 failure_cnt++;
01267 usleep(50000);
01268 continue;
01269 }
01270
01271 failure_cnt = 0;
01272 offset += written;
01273 remaining -= written;
01274 }
01275
01276 if (remaining > 0)
01277 return false;
01278
01279 return true;
01280 }
01281
01286 QDateTime MythDownloadManager::GetLastModified(const QString &url)
01287 {
01288
01289
01290
01291
01292
01293 static const char dateFormat[] = "ddd, dd MMM yyyy hh:mm:ss 'GMT'";
01294 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1')").arg(url));
01295 QDateTime result;
01296
01297 QDateTime now = QDateTime::currentDateTime();
01298
01299 QUrl cacheUrl = QUrl(url);
01300
01301
01302 QString redirectLoc;
01303 int limit = 0;
01304 while (!(redirectLoc = getHeader(cacheUrl, "Location")).isNull())
01305 {
01306 if (limit == CACHE_REDIRECTION_LIMIT)
01307 {
01308 LOG(VB_GENERAL, LOG_WARNING, QString("Cache Redirection limit "
01309 "reached for %1")
01310 .arg(cacheUrl.toString()));
01311 return result;
01312 }
01313 cacheUrl.setUrl(redirectLoc);
01314 limit++;
01315 }
01316
01317 m_infoLock->lock();
01318 QNetworkCacheMetaData urlData = m_manager->cache()->metaData(cacheUrl);
01319 m_infoLock->unlock();
01320
01321 if (urlData.isValid() &&
01322 ((!urlData.expirationDate().isValid()) ||
01323 (urlData.expirationDate().secsTo(now) < 0)))
01324 {
01325 if (urlData.lastModified().secsTo(now) <= 1800)
01326 {
01327 result = urlData.lastModified();
01328 }
01329 else
01330 {
01331 QString date = getHeader(urlData, "Date");
01332 if (!date.isNull())
01333 {
01334 QDateTime loadDate =
01335 QDateTime::fromString(date, dateFormat);
01336 loadDate.setTimeSpec(Qt::UTC);
01337 if (loadDate.secsTo(now) <= 720)
01338 {
01339 result = urlData.lastModified();
01340 }
01341 }
01342 }
01343 }
01344
01345 if (!result.isValid())
01346 {
01347 MythDownloadInfo *dlInfo = new MythDownloadInfo;
01348 dlInfo->m_url = url;
01349 dlInfo->m_syncMode = true;
01350
01351 dlInfo->m_requestType = kRequestHead;
01352
01353 if (downloadNow(dlInfo, false) && dlInfo->m_reply)
01354 {
01355 QVariant lastMod =
01356 dlInfo->m_reply->header(QNetworkRequest::LastModifiedHeader);
01357 if (lastMod.isValid())
01358 result = lastMod.toDateTime();
01359 }
01360
01361 delete dlInfo;
01362 }
01363
01364 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetLastModified('%1'): Result %2")
01365 .arg(url).arg(result.toString()));
01366
01367 return result;
01368 }
01369
01370
01374 void MythDownloadManager::loadCookieJar(const QString &filename)
01375 {
01376 QMutexLocker locker(&m_cookieLock);
01377
01378 MythCookieJar *jar = new MythCookieJar;
01379 jar->load(filename);
01380 m_manager->setCookieJar(jar);
01381 }
01382
01386 void MythDownloadManager::saveCookieJar(const QString &filename)
01387 {
01388 QMutexLocker locker(&m_cookieLock);
01389
01390 if (!m_manager->cookieJar())
01391 return;
01392
01393 MythCookieJar *jar = static_cast<MythCookieJar *>(m_manager->cookieJar());
01394 jar->save(filename);
01395 }
01396
01397 void MythDownloadManager::setCookieJar(QNetworkCookieJar *cookieJar)
01398 {
01399 QMutexLocker locker(&m_cookieLock);
01400 m_manager->setCookieJar(cookieJar);
01401 }
01402
01406 QNetworkCookieJar *MythDownloadManager::copyCookieJar(void)
01407 {
01408 QMutexLocker locker(&m_cookieLock);
01409
01410 if (!m_manager->cookieJar())
01411 return NULL;
01412
01413 MythCookieJar *inJar = static_cast<MythCookieJar *>(m_manager->cookieJar());
01414 MythCookieJar *outJar = new MythCookieJar(*inJar);
01415
01416 return static_cast<QNetworkCookieJar *>(outJar);
01417 }
01418
01422 void MythDownloadManager::refreshCookieJar(QNetworkCookieJar *jar)
01423 {
01424 QMutexLocker locker(&m_cookieLock);
01425 if (m_inCookieJar)
01426 delete m_inCookieJar;
01427
01428 MythCookieJar *inJar = static_cast<MythCookieJar *>(jar);
01429 MythCookieJar *outJar = new MythCookieJar(*inJar);
01430 m_inCookieJar = static_cast<QNetworkCookieJar *>(outJar);
01431
01432 QMutexLocker locker2(&m_queueWaitLock);
01433 m_queueWaitCond.wakeAll();
01434 }
01435
01438 void MythDownloadManager::updateCookieJar(void)
01439 {
01440 QMutexLocker locker(&m_cookieLock);
01441
01442 MythCookieJar *inJar = static_cast<MythCookieJar *>(m_inCookieJar);
01443 MythCookieJar *outJar = new MythCookieJar(*inJar);
01444 m_manager->setCookieJar(static_cast<QNetworkCookieJar *>(outJar));
01445
01446 delete m_inCookieJar;
01447 m_inCookieJar = NULL;
01448 }
01449
01450 QString MythDownloadManager::getHeader(const QUrl& url, const QString& header)
01451 {
01452 if (!m_manager || !m_manager->cache())
01453 return QString::null;
01454
01455 m_infoLock->lock();
01456 QNetworkCacheMetaData metadata = m_manager->cache()->metaData(url);
01457 m_infoLock->unlock();
01458
01459 return getHeader(metadata, header);
01460 }
01461
01467 QString MythDownloadManager::getHeader(const QNetworkCacheMetaData &cacheData,
01468 const QString& header)
01469 {
01470 QNetworkCacheMetaData::RawHeaderList headers = cacheData.rawHeaders();
01471 bool found = false;
01472 QNetworkCacheMetaData::RawHeaderList::iterator it = headers.begin();
01473 for (; !found && it != headers.end(); ++it)
01474 {
01475 if (QString((*it).first) == header)
01476 {
01477 found = true;
01478 return QString((*it).second);
01479 }
01480 }
01481
01482 return QString::null;
01483 }
01484
01485
01489 MythCookieJar::MythCookieJar(MythCookieJar &old)
01490 {
01491 const QList<QNetworkCookie> cookieList = old.allCookies();
01492 setAllCookies(cookieList);
01493 }
01494
01497 MythCookieJar::MythCookieJar()
01498 {
01499 }
01500
01504 void MythCookieJar::load(const QString &filename)
01505 {
01506 QList<QNetworkCookie> cookieList;
01507 QTextStream stream((QString *)&filename, QIODevice::ReadOnly);
01508 while (!stream.atEnd())
01509 {
01510 QString cookie = stream.readLine();
01511 cookieList << QNetworkCookie::parseCookies(cookie.toLocal8Bit());
01512 }
01513
01514 setAllCookies(cookieList);
01515 }
01516
01520 void MythCookieJar::save(const QString &filename)
01521 {
01522 QList<QNetworkCookie> cookieList = allCookies();
01523 QTextStream stream((QString *)&filename, QIODevice::WriteOnly);
01524
01525 for (QList<QNetworkCookie>::iterator it = cookieList.begin();
01526 it != cookieList.end(); ++it)
01527 {
01528 stream << (*it).toRawForm() << endl;
01529 }
01530 }
01531
01532
01533
01534