MythTV  0.26-pre
mythdownloadmanager.cpp
Go to the documentation of this file.
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 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends