svcore  1.9
FileSource.cpp
Go to the documentation of this file.
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
00002 
00003 /*
00004     Sonic Visualiser
00005     An audio file viewer and annotation editor.
00006     Centre for Digital Music, Queen Mary, University of London.
00007     This file copyright 2007 QMUL.
00008     
00009     This program is free software; you can redistribute it and/or
00010     modify it under the terms of the GNU General Public License as
00011     published by the Free Software Foundation; either version 2 of the
00012     License, or (at your option) any later version.  See the file
00013     COPYING included with this distribution for more information.
00014 */
00015 
00016 #include "FileSource.h"
00017 
00018 #include "base/TempDirectory.h"
00019 #include "base/Exceptions.h"
00020 #include "base/ProgressReporter.h"
00021 #include "system/System.h"
00022 
00023 #include <QNetworkAccessManager>
00024 #include <QNetworkReply>
00025 #include <QFileInfo>
00026 #include <QDir>
00027 #include <QCoreApplication>
00028 #include <QThreadStorage>
00029 
00030 #include <iostream>
00031 #include <cstdlib>
00032 
00033 #include <unistd.h>
00034 
00035 //#define DEBUG_FILE_SOURCE 1
00036 
00037 int
00038 FileSource::m_count = 0;
00039 
00040 QMutex
00041 FileSource::m_fileCreationMutex;
00042 
00043 FileSource::RemoteRefCountMap
00044 FileSource::m_refCountMap;
00045 
00046 FileSource::RemoteLocalMap
00047 FileSource::m_remoteLocalMap;
00048 
00049 QMutex
00050 FileSource::m_mapMutex;
00051 
00052 #ifdef DEBUG_FILE_SOURCE
00053 static int extantCount = 0;
00054 static std::map<QString, int> urlExtantCountMap;
00055 static void incCount(QString url) {
00056     ++extantCount;
00057     if (urlExtantCountMap.find(url) == urlExtantCountMap.end()) {
00058         urlExtantCountMap[url] = 1;
00059     } else {
00060         ++urlExtantCountMap[url];
00061     }
00062     cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl;
00063 }
00064 static void decCount(QString url) {
00065     --extantCount;
00066     --urlExtantCountMap[url];
00067     cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl;
00068 }
00069 #endif
00070 
00071 static QThreadStorage<QNetworkAccessManager *> nms;
00072 
00073 FileSource::FileSource(QString fileOrUrl, ProgressReporter *reporter,
00074                        QString preferredContentType) :
00075     m_rawFileOrUrl(fileOrUrl),
00076     m_url(fileOrUrl, QUrl::StrictMode),
00077     m_localFile(0),
00078     m_reply(0),
00079     m_preferredContentType(preferredContentType),
00080     m_ok(false),
00081     m_cancelled(false),
00082     m_lastStatus(0),
00083     m_resource(fileOrUrl.startsWith(':')),
00084     m_remote(isRemote(fileOrUrl)),
00085     m_done(false),
00086     m_leaveLocalFile(false),
00087     m_reporter(reporter),
00088     m_refCounted(false)
00089 {
00090     if (m_resource) { // qrc file
00091         m_url = QUrl("qrc" + fileOrUrl);
00092     }
00093 
00094     if (m_url.toString() == "") {
00095         m_url = QUrl(fileOrUrl, QUrl::TolerantMode);
00096     }
00097  
00098 #ifdef DEBUG_FILE_SOURCE
00099     cerr << "FileSource::FileSource(" << fileOrUrl << "): url <" << m_url.toString() << ">" << endl;
00100     incCount(m_url.toString());
00101 #endif
00102 
00103     if (!canHandleScheme(m_url)) {
00104         cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << endl;
00105         m_errorString = tr("Unsupported scheme in URL");
00106         return;
00107     }
00108 
00109     init();
00110 
00111     if (!isRemote() &&
00112         !isAvailable()) {
00113 #ifdef DEBUG_FILE_SOURCE
00114         cerr << "FileSource::FileSource: Failed to open local file with URL \"" << m_url.toString() << "\"; trying again assuming filename was encoded" << endl;
00115 #endif
00116         m_url = QUrl::fromEncoded(fileOrUrl.toLatin1());
00117 #ifdef DEBUG_FILE_SOURCE
00118         cerr << "FileSource::FileSource: URL is now \"" << m_url.toString() << "\"" << endl;
00119 #endif
00120         init();
00121     }
00122 
00123     if (isRemote() &&
00124         (fileOrUrl.contains('%') ||
00125          fileOrUrl.contains("--"))) { // for IDNA
00126 
00127         waitForStatus();
00128 
00129         if (!isAvailable()) {
00130 
00131             // The URL was created on the assumption that the string
00132             // was human-readable.  Let's try again, this time
00133             // assuming it was already encoded.
00134             cerr << "FileSource::FileSource: Failed to retrieve URL \""
00135                       << fileOrUrl 
00136                       << "\" as human-readable URL; "
00137                       << "trying again treating it as encoded URL"
00138                       << endl;
00139 
00140             // even though our cache file doesn't exist (because the
00141             // resource was 404), we still need to ensure we're no
00142             // longer associating a filename with this url in the
00143             // refcount map -- or createCacheFile will think we've
00144             // already done all the work and no request will be sent
00145             deleteCacheFile();
00146 
00147             m_url = QUrl::fromEncoded(fileOrUrl.toLatin1());
00148 
00149             m_ok = false;
00150             m_done = false;
00151             m_lastStatus = 0;
00152             init();
00153         }
00154     }
00155 
00156     if (!isRemote()) {
00157         emit statusAvailable();
00158         emit ready();
00159     }
00160 
00161 #ifdef DEBUG_FILE_SOURCE
00162     cerr << "FileSource::FileSource(string) exiting" << endl;
00163 #endif
00164 }
00165 
00166 FileSource::FileSource(QUrl url, ProgressReporter *reporter) :
00167     m_url(url),
00168     m_localFile(0),
00169     m_reply(0),
00170     m_ok(false),
00171     m_cancelled(false),
00172     m_lastStatus(0),
00173     m_resource(false),
00174     m_remote(isRemote(url.toString())),
00175     m_done(false),
00176     m_leaveLocalFile(false),
00177     m_reporter(reporter),
00178     m_refCounted(false)
00179 {
00180 #ifdef DEBUG_FILE_SOURCE
00181     cerr << "FileSource::FileSource(" << url.toString() << ") [as url]" << endl;
00182     incCount(m_url.toString());
00183 #endif
00184 
00185     if (!canHandleScheme(m_url)) {
00186         cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << endl;
00187         m_errorString = tr("Unsupported scheme in URL");
00188         return;
00189     }
00190 
00191     init();
00192 
00193 #ifdef DEBUG_FILE_SOURCE
00194     cerr << "FileSource::FileSource(url) exiting" << endl;
00195 #endif
00196 }
00197 
00198 FileSource::FileSource(const FileSource &rf) :
00199     QObject(),
00200     m_url(rf.m_url),
00201     m_localFile(0),
00202     m_reply(0),
00203     m_ok(rf.m_ok),
00204     m_cancelled(rf.m_cancelled),
00205     m_lastStatus(rf.m_lastStatus),
00206     m_resource(rf.m_resource),
00207     m_remote(rf.m_remote),
00208     m_done(false),
00209     m_leaveLocalFile(false),
00210     m_reporter(rf.m_reporter),
00211     m_refCounted(false)
00212 {
00213 #ifdef DEBUG_FILE_SOURCE
00214     cerr << "FileSource::FileSource(" << m_url.toString() << ") [copy ctor]" << endl;
00215     incCount(m_url.toString());
00216 #endif
00217 
00218     if (!canHandleScheme(m_url)) {
00219         cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << endl;
00220         m_errorString = tr("Unsupported scheme in URL");
00221         return;
00222     }
00223 
00224     if (!isRemote()) {
00225         m_localFilename = rf.m_localFilename;
00226     } else {
00227         QMutexLocker locker(&m_mapMutex);
00228 #ifdef DEBUG_FILE_SOURCE
00229         cerr << "FileSource::FileSource(copy ctor): ref count is "
00230                   << m_refCountMap[m_url] << endl;
00231 #endif
00232         if (m_refCountMap[m_url] > 0) {
00233             m_refCountMap[m_url]++;
00234 #ifdef DEBUG_FILE_SOURCE
00235             cerr << "raised it to " << m_refCountMap[m_url] << endl;
00236 #endif
00237             m_localFilename = m_remoteLocalMap[m_url];
00238             m_refCounted = true;
00239         } else {
00240             m_ok = false;
00241             m_lastStatus = 404;
00242         }
00243     }
00244 
00245     m_done = true;
00246 
00247 #ifdef DEBUG_FILE_SOURCE
00248     cerr << "FileSource::FileSource(" << m_url.toString() << ") [copy ctor]: note: local filename is \"" << m_localFilename << "\"" << endl;
00249 #endif
00250 
00251 #ifdef DEBUG_FILE_SOURCE
00252     cerr << "FileSource::FileSource(copy ctor) exiting" << endl;
00253 #endif
00254 }
00255 
00256 FileSource::~FileSource()
00257 {
00258 #ifdef DEBUG_FILE_SOURCE
00259     cerr << "FileSource(" << m_url.toString() << ")::~FileSource" << endl;
00260     decCount(m_url.toString());
00261 #endif
00262 
00263     cleanup();
00264 
00265     if (isRemote() && !m_leaveLocalFile) deleteCacheFile();
00266 }
00267 
00268 void
00269 FileSource::init()
00270 {
00271     { // check we have a QNetworkAccessManager
00272         QMutexLocker locker(&m_mapMutex);
00273         if (!nms.hasLocalData()) {
00274             nms.setLocalData(new QNetworkAccessManager());
00275         }
00276     }
00277 
00278     if (isResource()) {
00279 #ifdef DEBUG_FILE_SOURCE
00280         cerr << "FileSource::init: Is a resource" << endl;
00281 #endif
00282         QString resourceFile = m_url.toString();
00283         resourceFile.replace(QRegExp("^qrc:"), ":");
00284         
00285         if (!QFileInfo(resourceFile).exists()) {
00286 #ifdef DEBUG_FILE_SOURCE
00287             cerr << "FileSource::init: Resource file of this name does not exist, switching to non-resource URL" << endl;
00288 #endif
00289             m_url = resourceFile;
00290             m_resource = false;
00291         }
00292     }
00293 
00294     if (!isRemote() && !isResource()) {
00295 #ifdef DEBUG_FILE_SOURCE
00296         cerr << "FileSource::init: Not a remote URL" << endl;
00297 #endif
00298         bool literal = false;
00299         m_localFilename = m_url.toLocalFile();
00300 
00301         if (m_localFilename == "") {
00302             // QUrl may have mishandled the scheme (e.g. in a DOS path)
00303             m_localFilename = m_rawFileOrUrl;
00304 #ifdef DEBUG_FILE_SOURCE
00305             cerr << "FileSource::init: Trying literal local filename \""
00306                       << m_localFilename << "\"" << endl;
00307 #endif
00308             literal = true;
00309         }
00310         m_localFilename = QFileInfo(m_localFilename).absoluteFilePath();
00311 
00312 #ifdef DEBUG_FILE_SOURCE
00313         cerr << "FileSource::init: URL translates to absolute filename \""
00314                   << m_localFilename << "\" (with literal=" << literal << ")"
00315                   << endl;
00316 #endif
00317         m_ok = true;
00318         m_lastStatus = 200;
00319 
00320         if (!QFileInfo(m_localFilename).exists()) {
00321             if (literal) {
00322                 m_lastStatus = 404;
00323             } else {
00324 #ifdef DEBUG_FILE_SOURCE
00325                 cerr << "FileSource::init: Local file of this name does not exist, trying URL as a literal filename" << endl;
00326 #endif
00327                 // Again, QUrl may have been mistreating us --
00328                 // e.g. dropping a part that looks like query data
00329                 m_localFilename = m_rawFileOrUrl;
00330                 literal = true;
00331                 if (!QFileInfo(m_localFilename).exists()) {
00332                     m_lastStatus = 404;
00333                 }
00334             }
00335         }
00336 
00337         m_done = true;
00338         return;
00339     }
00340 
00341     if (createCacheFile()) {
00342 #ifdef DEBUG_FILE_SOURCE
00343         cerr << "FileSource::init: Already have this one" << endl;
00344 #endif
00345         m_ok = true;
00346         if (!QFileInfo(m_localFilename).exists()) {
00347             m_lastStatus = 404;
00348         } else {
00349             m_lastStatus = 200;
00350         }
00351         m_done = true;
00352         return;
00353     }
00354 
00355     if (m_localFilename == "") return;
00356 
00357     m_localFile = new QFile(m_localFilename);
00358     m_localFile->open(QFile::WriteOnly);
00359 
00360     if (isResource()) {
00361 
00362         // Absent resource file case was dealt with at the top -- this
00363         // is the successful case
00364 
00365         QString resourceFileName = m_url.toString();
00366         resourceFileName.replace(QRegExp("^qrc:"), ":");
00367         QFile resourceFile(resourceFileName);
00368         resourceFile.open(QFile::ReadOnly);
00369         QByteArray ba(resourceFile.readAll());
00370         
00371 #ifdef DEBUG_FILE_SOURCE
00372         cerr << "Copying " << ba.size() << " bytes from resource file to cache file" << endl;
00373 #endif
00374 
00375         qint64 written = m_localFile->write(ba);
00376         m_localFile->close();
00377         delete m_localFile;
00378         m_localFile = 0;
00379 
00380         if (written != ba.size()) {
00381 #ifdef DEBUG_FILE_SOURCE
00382             cerr << "Copy failed (wrote " << written << " bytes)" << endl;
00383 #endif
00384             m_ok = false;
00385             return;
00386         } else {
00387             m_ok = true;
00388             m_lastStatus = 200;
00389             m_done = true;
00390         }
00391 
00392     } else {
00393 
00394         QString scheme = m_url.scheme().toLower();
00395 
00396 #ifdef DEBUG_FILE_SOURCE
00397         cerr << "FileSource::init: Don't have local copy of \""
00398                   << m_url.toString() << "\", retrieving" << endl;
00399 #endif
00400 
00401         if (scheme == "http" || scheme == "https" || scheme == "ftp") {
00402             initRemote();
00403 #ifdef DEBUG_FILE_SOURCE
00404             cerr << "FileSource: initRemote returned" << endl;
00405 #endif
00406         } else {
00407             m_remote = false;
00408             m_ok = false;
00409         }
00410     }
00411 
00412     if (m_ok) {
00413         
00414         QMutexLocker locker(&m_mapMutex);
00415 
00416         if (m_refCountMap[m_url] > 0) {
00417             // someone else has been doing the same thing at the same time,
00418             // but has got there first
00419             cleanup();
00420             m_refCountMap[m_url]++;
00421 #ifdef DEBUG_FILE_SOURCE
00422             cerr << "FileSource::init: Another FileSource has got there first, abandoning our download and using theirs" << endl;
00423 #endif
00424             m_localFilename = m_remoteLocalMap[m_url];
00425             m_refCounted = true;
00426             m_ok = true;
00427             if (!QFileInfo(m_localFilename).exists()) {
00428                 m_lastStatus = 404;
00429             }
00430             m_done = true;
00431             return;
00432         }
00433 
00434         m_remoteLocalMap[m_url] = m_localFilename;
00435         m_refCountMap[m_url]++;
00436         m_refCounted = true;
00437 
00438         if (m_reporter && !m_done) {
00439             m_reporter->setMessage
00440                 (tr("Downloading %1...").arg(m_url.toString()));
00441             connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
00442             connect(this, SIGNAL(progress(int)),
00443                     m_reporter, SLOT(setProgress(int)));
00444         }
00445     }
00446 }
00447 
00448 void
00449 FileSource::initRemote()
00450 {
00451     m_ok = true;
00452 
00453     QNetworkRequest req;
00454     req.setUrl(m_url);
00455     
00456     if (m_preferredContentType != "") {
00457 #ifdef DEBUG_FILE_SOURCE
00458         cerr << "FileSource: indicating preferred content type of \""
00459                   << m_preferredContentType << "\"" << endl;
00460 #endif
00461         req.setRawHeader
00462             ("Accept",
00463              QString("%1, */*").arg(m_preferredContentType).toLatin1());
00464     }
00465 
00466     m_reply = nms.localData()->get(req);
00467 
00468     connect(m_reply, SIGNAL(readyRead()),
00469             this, SLOT(readyRead()));
00470     connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
00471             this, SLOT(replyFailed(QNetworkReply::NetworkError)));
00472     connect(m_reply, SIGNAL(finished()),
00473             this, SLOT(replyFinished()));
00474     connect(m_reply, SIGNAL(metaDataChanged()),
00475             this, SLOT(metaDataChanged()));
00476     connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
00477             this, SLOT(downloadProgress(qint64, qint64)));
00478 }
00479 
00480 void
00481 FileSource::cleanup()
00482 {
00483     if (m_done) {
00484         delete m_localFile; // does not actually delete the file
00485         m_localFile = 0;
00486     }
00487     m_done = true;
00488     if (m_reply) {
00489         QNetworkReply *r = m_reply;
00490         m_reply = 0;
00491         // Can only call abort() when there are no errors.
00492         if (r->error() == QNetworkReply::NoError) {
00493             r->abort();
00494         }
00495         r->deleteLater();
00496     }
00497     if (m_localFile) {
00498         delete m_localFile; // does not actually delete the file
00499         m_localFile = 0;
00500     }
00501 }
00502 
00503 bool
00504 FileSource::isRemote(QString fileOrUrl)
00505 {
00506     // Note that a "scheme" with length 1 is probably a DOS drive letter
00507     QString scheme = QUrl(fileOrUrl).scheme().toLower();
00508     if (scheme == "" || scheme == "file" || scheme.length() == 1) return false;
00509     return true;
00510 }
00511 
00512 bool
00513 FileSource::canHandleScheme(QUrl url)
00514 {
00515     // Note that a "scheme" with length 1 is probably a DOS drive letter
00516     QString scheme = url.scheme().toLower();
00517     return (scheme == "http" || scheme == "https" ||
00518             scheme == "ftp" || scheme == "file" || scheme == "qrc" ||
00519             scheme == "" || scheme.length() == 1);
00520 }
00521 
00522 bool
00523 FileSource::isAvailable()
00524 {
00525     waitForStatus();
00526     bool available = true;
00527     if (!m_ok) {
00528         available = false;
00529     } else {
00530         // http 2xx status codes mean success
00531         available = (m_lastStatus / 100 == 2);
00532     }
00533 #ifdef DEBUG_FILE_SOURCE
00534     cerr << "FileSource::isAvailable: " << (available ? "yes" : "no") << endl;
00535 #endif
00536     return available;
00537 }
00538 
00539 void
00540 FileSource::waitForStatus()
00541 {
00542     while (m_ok && (!m_done && m_lastStatus == 0)) {
00543 //        cerr << "waitForStatus: processing (last status " << m_lastStatus << ")" << endl;
00544         QCoreApplication::processEvents();
00545     }
00546 }
00547 
00548 void
00549 FileSource::waitForData()
00550 {
00551     while (m_ok && !m_done) {
00552 //        cerr << "FileSource::waitForData: calling QApplication::processEvents" << endl;
00553         QCoreApplication::processEvents();
00554         usleep(10000);
00555     }
00556 }
00557 
00558 void
00559 FileSource::setLeaveLocalFile(bool leave)
00560 {
00561     m_leaveLocalFile = leave;
00562 }
00563 
00564 bool
00565 FileSource::isOK() const
00566 {
00567     return m_ok;
00568 }
00569 
00570 bool
00571 FileSource::isDone() const
00572 {
00573     return m_done;
00574 }
00575 
00576 bool
00577 FileSource::wasCancelled() const
00578 {
00579     return m_cancelled;
00580 }
00581 
00582 bool
00583 FileSource::isResource() const
00584 {
00585     return m_resource;
00586 }
00587 
00588 bool
00589 FileSource::isRemote() const
00590 {
00591     return m_remote;
00592 }
00593 
00594 QString
00595 FileSource::getLocation() const
00596 {
00597     return m_url.toString();
00598 }
00599 
00600 QString
00601 FileSource::getLocalFilename() const
00602 {
00603     return m_localFilename;
00604 }
00605 
00606 QString
00607 FileSource::getBasename() const
00608 {
00609     return QFileInfo(m_localFilename).fileName();
00610 }
00611 
00612 QString
00613 FileSource::getContentType() const
00614 {
00615     return m_contentType;
00616 }
00617 
00618 QString
00619 FileSource::getExtension() const
00620 {
00621     if (m_localFilename != "") {
00622         return QFileInfo(m_localFilename).suffix().toLower();
00623     } else {
00624         return QFileInfo(m_url.toLocalFile()).suffix().toLower();
00625     }
00626 }
00627 
00628 QString
00629 FileSource::getErrorString() const
00630 {
00631     return m_errorString;
00632 }
00633 
00634 void
00635 FileSource::readyRead()
00636 {
00637     m_localFile->write(m_reply->readAll());
00638 }
00639 
00640 void
00641 FileSource::metaDataChanged()
00642 {
00643 #ifdef DEBUG_FILE_SOURCE
00644     cerr << "FileSource::metaDataChanged" << endl;
00645 #endif
00646 
00647     if (!m_reply) {
00648         cerr << "WARNING: FileSource::metaDataChanged() called without a reply object being known to us" << endl;
00649         return;
00650     }
00651 
00652     // Handle http transfer status codes.
00653 
00654     int status =
00655         m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
00656 
00657     // If this is a redirection (3xx) code, do the redirect
00658     if (status / 100 == 3) {
00659         QString location = m_reply->header
00660             (QNetworkRequest::LocationHeader).toString();
00661 #ifdef DEBUG_FILE_SOURCE
00662         cerr << "FileSource::metaDataChanged: redirect to \""
00663                   << location << "\" received" << endl;
00664 #endif
00665         if (location != "") {
00666             QUrl newUrl(location);
00667             if (newUrl != m_url) {
00668                 cleanup();
00669                 deleteCacheFile();
00670 #ifdef DEBUG_FILE_SOURCE
00671                 decCount(m_url.toString());
00672                 incCount(newUrl.toString());
00673 #endif
00674                 m_url = newUrl;
00675                 m_localFile = 0;
00676                 m_lastStatus = 0;
00677                 m_done = false;
00678                 m_refCounted = false;
00679                 init();
00680                 return;
00681             }
00682         }
00683     }
00684 
00685     m_lastStatus = status;
00686 
00687     // 400 and up are failures, get the error string
00688     if (m_lastStatus / 100 >= 4) {
00689         m_errorString = QString("%1 %2")
00690             .arg(status)
00691             .arg(m_reply->attribute
00692                  (QNetworkRequest::HttpReasonPhraseAttribute).toString());
00693 #ifdef DEBUG_FILE_SOURCE
00694         cerr << "FileSource::metaDataChanged: "
00695                   << m_errorString << endl;
00696 #endif
00697     } else {
00698 #ifdef DEBUG_FILE_SOURCE
00699         cerr << "FileSource::metaDataChanged: "
00700                   << m_lastStatus << endl;
00701 #endif
00702         m_contentType =
00703             m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
00704     }
00705     emit statusAvailable();
00706 }
00707 
00708 void
00709 FileSource::downloadProgress(qint64 done, qint64 total)
00710 {
00711     int percent = int((double(done) / double(total)) * 100.0 - 0.1);
00712     emit progress(percent);
00713 }
00714 
00715 void
00716 FileSource::cancelled()
00717 {
00718     m_done = true;
00719     cleanup();
00720 
00721     m_ok = false;
00722     m_cancelled = true;
00723     m_errorString = tr("Download cancelled");
00724 }
00725 
00726 void
00727 FileSource::replyFinished()
00728 {
00729     emit progress(100);
00730 
00731 #ifdef DEBUG_FILE_SOURCE
00732     cerr << "FileSource::replyFinished()" << endl;
00733 #endif
00734 
00735     if (m_done) return;
00736 
00737     QString scheme = m_url.scheme().toLower();
00738     // For ftp transfers, replyFinished() will be called on success.
00739     // metaDataChanged() is never called for ftp transfers.
00740     if (scheme == "ftp") {
00741         m_lastStatus = 200;  // http ok
00742     }
00743 
00744     bool error = (m_lastStatus / 100 >= 4);
00745 
00746     cleanup();
00747 
00748     if (!error) {
00749         QFileInfo fi(m_localFilename);
00750         if (!fi.exists()) {
00751             m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
00752             error = true;
00753         } else if (fi.size() == 0) {
00754             m_errorString = tr("File contains no data!");
00755             error = true;
00756         }
00757     }
00758 
00759     if (error) {
00760 #ifdef DEBUG_FILE_SOURCE
00761         cerr << "FileSource::done: error is " << error << ", deleting cache file" << endl;
00762 #endif
00763         deleteCacheFile();
00764     }
00765 
00766     m_ok = !error;
00767     if (m_localFile) m_localFile->flush();
00768     m_done = true;
00769     emit ready();
00770 }
00771 
00772 void
00773 FileSource::replyFailed(QNetworkReply::NetworkError)
00774 {
00775     emit progress(100);
00776     if (!m_reply) {
00777         cerr << "WARNING: FileSource::replyFailed() called without a reply object being known to us" << endl;
00778     } else {
00779         m_errorString = m_reply->errorString();
00780     }
00781     m_ok = false;
00782     m_done = true;
00783     cleanup();
00784     emit ready();
00785 }
00786 
00787 void
00788 FileSource::deleteCacheFile()
00789 {
00790 #ifdef DEBUG_FILE_SOURCE
00791     cerr << "FileSource::deleteCacheFile(\"" << m_localFilename << "\")" << endl;
00792 #endif
00793 
00794     cleanup();
00795 
00796     if (m_localFilename == "") {
00797         return;
00798     }
00799 
00800     if (!isRemote()) {
00801 #ifdef DEBUG_FILE_SOURCE
00802         cerr << "not a cache file" << endl;
00803 #endif
00804         return;
00805     }
00806 
00807     if (m_refCounted) {
00808 
00809         QMutexLocker locker(&m_mapMutex);
00810         m_refCounted = false;
00811 
00812         if (m_refCountMap[m_url] > 0) {
00813             m_refCountMap[m_url]--;
00814 #ifdef DEBUG_FILE_SOURCE
00815             cerr << "reduced ref count to " << m_refCountMap[m_url] << endl;
00816 #endif
00817             if (m_refCountMap[m_url] > 0) {
00818                 m_done = true;
00819                 return;
00820             }
00821         }
00822     }
00823 
00824     m_fileCreationMutex.lock();
00825 
00826     if (!QFile(m_localFilename).remove()) {
00827 #ifdef DEBUG_FILE_SOURCE
00828         cerr << "FileSource::deleteCacheFile: ERROR: Failed to delete file \"" << m_localFilename << "\"" << endl;
00829 #endif
00830     } else {
00831 #ifdef DEBUG_FILE_SOURCE
00832         cerr << "FileSource::deleteCacheFile: Deleted cache file \"" << m_localFilename << "\"" << endl;
00833 #endif
00834         m_localFilename = "";
00835     }
00836 
00837     m_fileCreationMutex.unlock();
00838 
00839     m_done = true;
00840 }
00841 
00842 bool
00843 FileSource::createCacheFile()
00844 {
00845     {
00846         QMutexLocker locker(&m_mapMutex);
00847 
00848 #ifdef DEBUG_FILE_SOURCE
00849         cerr << "FileSource::createCacheFile: refcount is " << m_refCountMap[m_url] << endl;
00850 #endif
00851 
00852         if (m_refCountMap[m_url] > 0) {
00853             m_refCountMap[m_url]++;
00854             m_localFilename = m_remoteLocalMap[m_url];
00855 #ifdef DEBUG_FILE_SOURCE
00856             cerr << "raised it to " << m_refCountMap[m_url] << endl;
00857 #endif
00858             m_refCounted = true;
00859             return true;
00860         }
00861     }
00862 
00863     QDir dir;
00864     try {
00865         dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
00866     } catch (DirectoryCreationFailed f) {
00867 #ifdef DEBUG_FILE_SOURCE
00868         cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << endl;
00869 #endif
00870         return false;
00871     }
00872 
00873     QString filepart = m_url.path().section('/', -1, -1,
00874                                             QString::SectionSkipEmpty);
00875 
00876     QString extension = "";
00877     if (filepart.contains('.')) extension = filepart.section('.', -1);
00878 
00879     QString base = filepart;
00880     if (extension != "") {
00881         base = base.left(base.length() - extension.length() - 1);
00882     }
00883     if (base == "") base = "remote";
00884 
00885     QString filename;
00886 
00887     if (extension == "") {
00888         filename = base;
00889     } else {
00890         filename = QString("%1.%2").arg(base).arg(extension);
00891     }
00892 
00893     QString filepath(dir.filePath(filename));
00894 
00895 #ifdef DEBUG_FILE_SOURCE
00896     cerr << "FileSource::createCacheFile: URL is \"" << m_url.toString() << "\", dir is \"" << dir.path() << "\", base \"" << base << "\", extension \"" << extension << "\", filebase \"" << filename << "\", filename \"" << filepath << "\"" << endl;
00897 #endif
00898 
00899     QMutexLocker fcLocker(&m_fileCreationMutex);
00900 
00901     ++m_count;
00902 
00903     if (QFileInfo(filepath).exists() ||
00904         !QFile(filepath).open(QFile::WriteOnly)) {
00905 
00906 #ifdef DEBUG_FILE_SOURCE
00907         cerr << "FileSource::createCacheFile: Failed to create local file \""
00908                   << filepath << "\" for URL \""
00909                   << m_url.toString() << "\" (or file already exists): appending suffix instead" << endl;
00910 #endif
00911 
00912         if (extension == "") {
00913             filename = QString("%1_%2").arg(base).arg(m_count);
00914         } else {
00915             filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
00916         }
00917         filepath = dir.filePath(filename);
00918 
00919         if (QFileInfo(filepath).exists() ||
00920             !QFile(filepath).open(QFile::WriteOnly)) {
00921 
00922 #ifdef DEBUG_FILE_SOURCE
00923             cerr << "FileSource::createCacheFile: ERROR: Failed to create local file \""
00924                       << filepath << "\" for URL \""
00925                       << m_url.toString() << "\" (or file already exists)" << endl;
00926 #endif
00927 
00928             return false;
00929         }
00930     }
00931 
00932 #ifdef DEBUG_FILE_SOURCE
00933     cerr << "FileSource::createCacheFile: url "
00934               << m_url.toString() << " -> local filename "
00935               << filepath << endl;
00936 #endif
00937     
00938     m_localFilename = filepath;
00939 
00940     return false;
00941 }
00942