|
MythTV
0.26-pre
|
00001 // qt 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 // libmythbase 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 // Check once more in case the download manager was created 00168 // while we were securing the lock. 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 // Set the proxy for the manager to be the application default proxy, 00246 // which has already been setup 00247 m_manager->setProxy(*m_proxy); 00248 00249 // make sure the cookieJar is created in the same thread as the manager 00250 // and set its parent to NULL so it can be shared between managers 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 // Push request to the end of the queue to let others process. 00299 // If this is the only item in the queue, force the loop to 00300 // wait a little. 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 // Prefer the in-cache item if one exists and it is less than 5 minutes 00667 // old and it will not expire in the next 10 seconds 00668 QDateTime now = QDateTime::currentDateTime(); 00669 00670 // Handle redirects, we want the metadata of the file headers 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 // timeout myth:// RemoteFile transfers 20 seconds from now 00796 // timeout non-myth:// QNetworkAccessManager transfers 10 seconds after 00797 // their last progress update 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; // Prevent downloadFinished() from updating 00820 dlInfo->m_syncMode = false; // Let downloadFinished() cleanup for us 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 // this shouldn't happen 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 // HACK Insert a Date header into the cached metadata if one doesn't 01048 // already exist 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 // End HACK 01082 01083 dlInfo->m_redirectedTo.clear(); 01084 01085 int dataSize = -1; 01086 01087 // If we downloaded via the QNetworkAccessManager 01088 // AND the caller isn't handling the reply directly 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) // If we downloaded via RemoteFile 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 // else we downloaded via QNetworkAccessManager 01128 // AND the caller is handling the reply 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 // placeholder for error string 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 // If the header has not expired and 01289 // the last modification date is less than 30 minutes old or if 01290 // the cache object is less than 5 minutes old, 01291 // then use the cached header otherwise redownload the header 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 // Deal with redirects, we want the cached data for the final url 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 // Head request, we only want to inspect the headers 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 /* vim: set expandtab tabstop=4 shiftwidth=4: */ 01534
1.7.6.1