Chris@208: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@208: 
Chris@208: /*
Chris@208:     Sonic Visualiser
Chris@208:     An audio file viewer and annotation editor.
Chris@208:     Centre for Digital Music, Queen Mary, University of London.
Chris@208:     This file copyright 2007 QMUL.
Chris@208:     
Chris@208:     This program is free software; you can redistribute it and/or
Chris@208:     modify it under the terms of the GNU General Public License as
Chris@208:     published by the Free Software Foundation; either version 2 of the
Chris@208:     License, or (at your option) any later version.  See the file
Chris@208:     COPYING included with this distribution for more information.
Chris@208: */
Chris@208: 
Chris@317: #include "FileSource.h"
Chris@357: 
Chris@208: #include "base/TempDirectory.h"
Chris@208: #include "base/Exceptions.h"
Chris@392: #include "base/ProgressReporter.h"
Chris@502: #include "system/System.h"
Chris@208: 
Chris@208: #include <QHttp>
Chris@208: #include <QFtp>
Chris@208: #include <QFileInfo>
Chris@208: #include <QDir>
Chris@392: #include <QCoreApplication>
Chris@210: #include <QHttpResponseHeader>
Chris@208: 
Chris@208: #include <iostream>
Chris@405: #include <cstdlib>
Chris@208: 
Chris@608: #include <unistd.h>
Chris@608: 
Chris@528: //#define DEBUG_FILE_SOURCE 1
Chris@327: 
Chris@208: int
Chris@317: FileSource::m_count = 0;
Chris@208: 
Chris@208: QMutex
Chris@317: FileSource::m_fileCreationMutex;
Chris@208: 
Chris@317: FileSource::RemoteRefCountMap
Chris@317: FileSource::m_refCountMap;
Chris@304: 
Chris@317: FileSource::RemoteLocalMap
Chris@317: FileSource::m_remoteLocalMap;
Chris@304: 
Chris@304: QMutex
Chris@317: FileSource::m_mapMutex;
Chris@304: 
Chris@529: #ifdef DEBUG_FILE_SOURCE
Chris@529: static int extantCount = 0;
Chris@529: static std::map<QString, int> urlExtantCountMap;
Chris@529: static void incCount(QString url) {
Chris@529:     ++extantCount;
Chris@529:     if (urlExtantCountMap.find(url) == urlExtantCountMap.end()) {
Chris@529:         urlExtantCountMap[url] = 1;
Chris@529:     } else {
Chris@529:         ++urlExtantCountMap[url];
Chris@529:     }
Chris@529:     std::cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << std::endl;
Chris@529: }
Chris@529: static void decCount(QString url) {
Chris@529:     --extantCount;
Chris@529:     --urlExtantCountMap[url];
Chris@529:     std::cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << std::endl;
Chris@529: }
Chris@529: #endif
Chris@529: 
Chris@520: FileSource::FileSource(QString fileOrUrl, ProgressReporter *reporter,
Chris@520:                        QString preferredContentType) :
Chris@614:     m_url(fileOrUrl, QUrl::StrictMode),
Chris@316:     m_ftp(0),
Chris@316:     m_http(0),
Chris@316:     m_localFile(0),
Chris@520:     m_preferredContentType(preferredContentType),
Chris@316:     m_ok(false),
Chris@316:     m_lastStatus(0),
Chris@316:     m_remote(isRemote(fileOrUrl)),
Chris@316:     m_done(false),
Chris@316:     m_leaveLocalFile(false),
Chris@392:     m_reporter(reporter),
Chris@316:     m_refCounted(false)
Chris@316: {
Chris@661:     if (m_url.toString() == "") {
Chris@661:         m_url = QUrl(fileOrUrl, QUrl::TolerantMode);
Chris@661:     }
Chris@661:  
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::FileSource(" << fileOrUrl << "): url <" << m_url.toString() << ">" << endl;
Chris@529:     incCount(m_url.toString());
Chris@327: #endif
Chris@316: 
Chris@316:     if (!canHandleScheme(m_url)) {
Chris@690:         SVDEBUG << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << endl;
Chris@316:         m_errorString = tr("Unsupported scheme in URL");
Chris@316:         return;
Chris@316:     }
Chris@316: 
Chris@357:     init();
Chris@316: 
Chris@614:     if (!isRemote() &&
Chris@614:         !isAvailable()) {
Chris@614: #ifdef DEBUG_FILE_SOURCE
Chris@686:         std::cerr << "FileSource::FileSource: Failed to open local file with URL \"" << m_url.toString() << "; trying again assuming filename was encoded" << std::endl;
Chris@614: #endif
Chris@616:         m_url = QUrl::fromEncoded(fileOrUrl.toAscii());
Chris@614:         init();
Chris@614:     }
Chris@614: 
Chris@316:     if (isRemote() &&
Chris@316:         (fileOrUrl.contains('%') ||
Chris@316:          fileOrUrl.contains("--"))) { // for IDNA
Chris@316: 
Chris@316:         waitForStatus();
Chris@316: 
Chris@316:         if (!isAvailable()) {
Chris@336: 
Chris@316:             // The URL was created on the assumption that the string
Chris@316:             // was human-readable.  Let's try again, this time
Chris@316:             // assuming it was already encoded.
Chris@317:             std::cerr << "FileSource::FileSource: Failed to retrieve URL \""
Chris@316:                       << fileOrUrl.toStdString() 
Chris@316:                       << "\" as human-readable URL; "
Chris@316:                       << "trying again treating it as encoded URL"
Chris@316:                       << std::endl;
Chris@336: 
Chris@336:             // even though our cache file doesn't exist (because the
Chris@336:             // resource was 404), we still need to ensure we're no
Chris@336:             // longer associating a filename with this url in the
Chris@336:             // refcount map -- or createCacheFile will think we've
Chris@336:             // already done all the work and no request will be sent
Chris@336:             deleteCacheFile();
Chris@336: 
Chris@316:             m_url.setEncodedUrl(fileOrUrl.toAscii());
Chris@336: 
Chris@336:             m_ok = false;
Chris@336:             m_done = false;
Chris@336:             m_lastStatus = 0;
Chris@357:             init();
Chris@316:         }
Chris@316:     }
Chris@325: 
Chris@325:     if (!isRemote()) {
Chris@325:         emit statusAvailable();
Chris@325:         emit ready();
Chris@325:     }
Chris@497: 
Chris@504: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::FileSource(string) exiting" << endl;
Chris@504: #endif
Chris@316: }
Chris@316: 
Chris@469: FileSource::FileSource(QUrl url, ProgressReporter *reporter) :
Chris@304:     m_url(url),
Chris@208:     m_ftp(0),
Chris@208:     m_http(0),
Chris@208:     m_localFile(0),
Chris@208:     m_ok(false),
Chris@210:     m_lastStatus(0),
Chris@316:     m_remote(isRemote(url.toString())),
Chris@208:     m_done(false),
Chris@316:     m_leaveLocalFile(false),
Chris@392:     m_reporter(reporter),
Chris@316:     m_refCounted(false)
Chris@208: {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::FileSource(" << url.toString() << ") [as url]" << endl;
Chris@529:     incCount(m_url.toString());
Chris@327: #endif
Chris@316: 
Chris@316:     if (!canHandleScheme(m_url)) {
Chris@690:         SVDEBUG << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << endl;
Chris@316:         m_errorString = tr("Unsupported scheme in URL");
Chris@208:         return;
Chris@208:     }
Chris@208: 
Chris@357:     init();
Chris@497: 
Chris@504: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::FileSource(url) exiting" << endl;
Chris@504: #endif
Chris@316: }
Chris@304: 
Chris@317: FileSource::FileSource(const FileSource &rf) :
Chris@316:     QObject(),
Chris@316:     m_url(rf.m_url),
Chris@316:     m_ftp(0),
Chris@316:     m_http(0),
Chris@316:     m_localFile(0),
Chris@316:     m_ok(rf.m_ok),
Chris@316:     m_lastStatus(rf.m_lastStatus),
Chris@316:     m_remote(rf.m_remote),
Chris@316:     m_done(false),
Chris@316:     m_leaveLocalFile(false),
Chris@392:     m_reporter(rf.m_reporter),
Chris@316:     m_refCounted(false)
Chris@316: {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::FileSource(" << m_url.toString() << ") [copy ctor]" << endl;
Chris@529:     incCount(m_url.toString());
Chris@327: #endif
Chris@304: 
Chris@316:     if (!canHandleScheme(m_url)) {
Chris@690:         SVDEBUG << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << endl;
Chris@316:         m_errorString = tr("Unsupported scheme in URL");
Chris@304:         return;
Chris@304:     }
Chris@304: 
Chris@469:     if (!isRemote()) {
Chris@316:         m_localFilename = rf.m_localFilename;
Chris@316:     } else {
Chris@316:         QMutexLocker locker(&m_mapMutex);
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::FileSource(copy ctor): ref count is "
Chris@687:                   << m_refCountMap[m_url] << endl;
Chris@327: #endif
Chris@316:         if (m_refCountMap[m_url] > 0) {
Chris@316:             m_refCountMap[m_url]++;
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@316:             std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl;
Chris@327: #endif
Chris@316:             m_localFilename = m_remoteLocalMap[m_url];
Chris@316:             m_refCounted = true;
Chris@316:         } else {
Chris@316:             m_ok = false;
Chris@316:             m_lastStatus = 404;
Chris@316:         }
Chris@316:     }
Chris@316: 
Chris@316:     m_done = true;
Chris@497: 
Chris@504: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::FileSource(" << m_url.toString() << ") [copy ctor]: note: local filename is \"" << m_localFilename << "\"" << endl;
Chris@527: #endif
Chris@527: 
Chris@527: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::FileSource(copy ctor) exiting" << endl;
Chris@504: #endif
Chris@316: }
Chris@316: 
Chris@317: FileSource::~FileSource()
Chris@316: {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@686:     std::cerr << "FileSource(" << m_url.toString() << ")::~FileSource" << std::endl;
Chris@529:     decCount(m_url.toString());
Chris@327: #endif
Chris@316: 
Chris@316:     cleanup();
Chris@316: 
Chris@469:     if (isRemote() && !m_leaveLocalFile) deleteCacheFile();
Chris@316: }
Chris@316: 
Chris@316: void
Chris@357: FileSource::init()
Chris@316: {
Chris@316:     if (!isRemote()) {
Chris@355: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::init: Not a remote URL" << endl;
Chris@355: #endif
Chris@355:         bool literal = false;
Chris@316:         m_localFilename = m_url.toLocalFile();
Chris@342:         if (m_localFilename == "") {
Chris@342:             // QUrl may have mishandled the scheme (e.g. in a DOS path)
Chris@342:             m_localFilename = m_url.toString();
Chris@355:             literal = true;
Chris@342:         }
Chris@439:         m_localFilename = QFileInfo(m_localFilename).absoluteFilePath();
Chris@439: 
Chris@355: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::init: URL translates to local filename \""
Chris@687:                   << m_localFilename << "\" (with literal=" << literal << ")" << endl;
Chris@355: #endif
Chris@316:         m_ok = true;
Chris@355:         m_lastStatus = 200;
Chris@355: 
Chris@316:         if (!QFileInfo(m_localFilename).exists()) {
Chris@355:             if (literal) {
Chris@355:                 m_lastStatus = 404;
Chris@355:             } else {
Chris@614: #ifdef DEBUG_FILE_SOURCE
Chris@690:                 SVDEBUG << "FileSource::init: Local file of this name does not exist, trying URL as a literal filename" << endl;
Chris@614: #endif
Chris@355:                 // Again, QUrl may have been mistreating us --
Chris@355:                 // e.g. dropping a part that looks like query data
Chris@355:                 m_localFilename = m_url.toString();
Chris@355:                 literal = true;
Chris@355:                 if (!QFileInfo(m_localFilename).exists()) {
Chris@355:                     m_lastStatus = 404;
Chris@355:                 }
Chris@355:             }
Chris@316:         }
Chris@355: 
Chris@316:         m_done = true;
Chris@316:         return;
Chris@316:     }
Chris@316: 
Chris@316:     if (createCacheFile()) {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::init: Already have this one" << endl;
Chris@327: #endif
Chris@316:         m_ok = true;
Chris@316:         if (!QFileInfo(m_localFilename).exists()) {
Chris@316:             m_lastStatus = 404;
Chris@316:         } else {
Chris@316:             m_lastStatus = 200;
Chris@316:         }
Chris@316:         m_done = true;
Chris@316:         return;
Chris@316:     }
Chris@316: 
Chris@208:     if (m_localFilename == "") return;
Chris@208:     m_localFile = new QFile(m_localFilename);
Chris@208:     m_localFile->open(QFile::WriteOnly);
Chris@208: 
Chris@316:     QString scheme = m_url.scheme().toLower();
Chris@316: 
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::init: Don't have local copy of \""
Chris@687:               << m_url.toString() << "\", retrieving" << endl;
Chris@327: #endif
Chris@208: 
Chris@208:     if (scheme == "http") {
Chris@316:         initHttp();
Chris@520: #ifdef DEBUG_FILE_SOURCE
Chris@357:         std::cerr << "FileSource: initHttp succeeded" << std::endl;
Chris@520: #endif
Chris@208:     } else if (scheme == "ftp") {
Chris@316:         initFtp();
Chris@316:     } else {
Chris@316:         m_remote = false;
Chris@316:         m_ok = false;
Chris@208:     }
Chris@208: 
Chris@208:     if (m_ok) {
Chris@316:         
Chris@316:         QMutexLocker locker(&m_mapMutex);
Chris@316: 
Chris@316:         if (m_refCountMap[m_url] > 0) {
Chris@316:             // someone else has been doing the same thing at the same time,
Chris@316:             // but has got there first
Chris@316:             cleanup();
Chris@316:             m_refCountMap[m_url]++;
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:             SVDEBUG << "FileSource::init: Another FileSource has got there first, abandoning our download and using theirs" << endl;
Chris@327: #endif
Chris@316:             m_localFilename = m_remoteLocalMap[m_url];
Chris@316:             m_refCounted = true;
Chris@316:             m_ok = true;
Chris@316:             if (!QFileInfo(m_localFilename).exists()) {
Chris@316:                 m_lastStatus = 404;
Chris@316:             }
Chris@316:             m_done = true;
Chris@316:             return;
Chris@316:         }
Chris@304: 
Chris@304:         m_remoteLocalMap[m_url] = m_localFilename;
Chris@304:         m_refCountMap[m_url]++;
Chris@316:         m_refCounted = true;
Chris@304: 
Chris@392:         if (m_reporter) {
Chris@392:             m_reporter->setMessage
Chris@392:                 (tr("Downloading %1...").arg(m_url.toString()));
Chris@392:             connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@357:             connect(this, SIGNAL(progress(int)),
Chris@392:                     m_reporter, SLOT(setProgress(int)));
Chris@316:         }
Chris@208:     }
Chris@208: }
Chris@208: 
Chris@316: void
Chris@317: FileSource::initHttp()
Chris@208: {
Chris@316:     m_ok = true;
Chris@497:     int port = m_url.port();
Chris@497:     m_http = new QHttp(m_url.host(), port < 0 ? 80 : port);
Chris@316:     connect(m_http, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@316:     connect(m_http, SIGNAL(dataReadProgress(int, int)),
Chris@316:             this, SLOT(dataReadProgress(int, int)));
Chris@316:     connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)),
Chris@316:             this, SLOT(httpResponseHeaderReceived(const QHttpResponseHeader &)));
Chris@316: 
Chris@316:     // I don't quite understand this.  url.path() returns a path
Chris@316:     // without percent encoding; for example, spaces appear as
Chris@316:     // literal spaces.  This generally won't work if sent to the
Chris@316:     // server directly.  You can retrieve a correctly encoded URL
Chris@316:     // from QUrl using url.toEncoded(), but that gives you the
Chris@316:     // whole URL; there doesn't seem to be any way to retrieve
Chris@316:     // only an encoded path.  Furthermore there doesn't seem to be
Chris@316:     // any way to convert a retrieved path into an encoded path
Chris@316:     // without explicitly specifying that you don't want the path
Chris@316:     // separators ("/") to be encoded.  (Besides being painful to
Chris@316:     // manage, I don't see how this can work correctly in any case
Chris@316:     // where a percent-encoded "/" is supposed to appear within a
Chris@316:     // path element?)  There also seems to be no way to retrieve
Chris@316:     // the path plus query string, i.e. everything that I need to
Chris@316:     // send to the HTTP server.  And no way for QHttp to take a
Chris@316:     // QUrl argument.  I'm obviously missing something.
Chris@316: 
Chris@316:     // So, two ways to do this: query the bits from the URL,
Chris@316:     // encode them individually, and glue them back together
Chris@316:     // again...
Chris@316: /*
Chris@316:     QString path = QUrl::toPercentEncoding(m_url.path(), "/");
Chris@316:     QList<QPair<QString, QString> > query = m_url.queryItems();
Chris@316:     if (!query.empty()) {
Chris@316:         QStringList q2;
Chris@316:         for (QList<QPair<QString, QString> >::iterator i = query.begin();
Chris@316:              i != query.end(); ++i) {
Chris@316:             q2.push_back(QString("%1=%3")
Chris@316:                          .arg(QString(QUrl::toPercentEncoding(i->first)))
Chris@316:                          .arg(QString(QUrl::toPercentEncoding(i->second))));
Chris@316:         }
Chris@316:         path = QString("%1%2%3")
Chris@316:             .arg(path).arg("?")
Chris@316:             .arg(q2.join("&"));
Chris@316:     }
Chris@316: */
Chris@316: 
Chris@316:     // ...or, much simpler but relying on knowledge about the
Chris@316:     // scheme://host/path/path/query etc format of the URL, we can
Chris@316:     // get the whole URL ready-encoded and then split it on "/" as
Chris@316:     // appropriate...
Chris@316:         
Chris@316:     QString path = "/" + QString(m_url.toEncoded()).section('/', 3);
Chris@316: 
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource: path is \""
Chris@687:               << path << "\"" << endl;
Chris@327: #endif
Chris@316:         
Chris@520:     if (m_preferredContentType == "") {
Chris@520:         m_http->get(path, m_localFile);
Chris@520:     } else {
Chris@520: #ifdef DEBUG_FILE_SOURCE
Chris@520:         std::cerr << "FileSource: indicating preferred content type of \""
Chris@686:                   << m_preferredContentType << "\"" << std::endl;
Chris@520: #endif
Chris@520:         QHttpRequestHeader header("GET", path);
Chris@520:         header.setValue("Host", m_url.host());
Chris@520:         header.setValue("Accept", QString("%1, */*").arg(m_preferredContentType));
Chris@520:         m_http->request(header, 0, m_localFile);
Chris@520:     }
Chris@316: }
Chris@316: 
Chris@316: void
Chris@317: FileSource::initFtp()
Chris@316: {
Chris@316:     m_ok = true;
Chris@316:     m_ftp = new QFtp;
Chris@316:     connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@316:     connect(m_ftp, SIGNAL(commandFinished(int, bool)),
Chris@316:             this, SLOT(ftpCommandFinished(int, bool)));
Chris@316:     connect(m_ftp, SIGNAL(dataTransferProgress(qint64, qint64)),
Chris@316:             this, SLOT(dataTransferProgress(qint64, qint64)));
Chris@316:     m_ftp->connectToHost(m_url.host(), m_url.port(21));
Chris@316:     
Chris@316:     QString username = m_url.userName();
Chris@316:     if (username == "") {
Chris@316:         username = "anonymous";
Chris@316:     }
Chris@316:     
Chris@316:     QString password = m_url.password();
Chris@316:     if (password == "") {
Chris@316:         password = QString("%1@%2").arg(getenv("USER")).arg(getenv("HOST"));
Chris@316:     }
Chris@316:     
Chris@316:     m_ftp->login(username, password);
Chris@316:     
Chris@316:     QString dirpath = m_url.path().section('/', 0, -2);
Chris@316:     QString filename = m_url.path().section('/', -1);
Chris@316:     
Chris@316:     if (dirpath == "") dirpath = "/";
Chris@316:     m_ftp->cd(dirpath);
Chris@316:     m_ftp->get(filename, m_localFile);
Chris@211: }
Chris@211: 
Chris@211: void
Chris@317: FileSource::cleanup()
Chris@211: {
Chris@470:     if (m_done) {
Chris@470:         delete m_localFile; // does not actually delete the file
Chris@470:         m_localFile = 0;
Chris@470:     }
Chris@211:     m_done = true;
Chris@214:     if (m_http) {
Chris@287:         QHttp *h = m_http;
Chris@214:         m_http = 0;
Chris@287:         h->abort();
Chris@287:         h->deleteLater();
Chris@214:     }
Chris@214:     if (m_ftp) {
Chris@287:         QFtp *f = m_ftp;
Chris@214:         m_ftp = 0;
Chris@287:         f->abort();
Chris@287:         f->deleteLater();
Chris@214:     }
Chris@470:     if (m_localFile) {
Chris@470:         delete m_localFile; // does not actually delete the file
Chris@470:         m_localFile = 0;
Chris@470:     }
Chris@208: }
Chris@208: 
Chris@208: bool
Chris@317: FileSource::isRemote(QString fileOrUrl)
Chris@304: {
Chris@342:     // Note that a "scheme" with length 1 is probably a DOS drive letter
Chris@316:     QString scheme = QUrl(fileOrUrl).scheme().toLower();
Chris@342:     if (scheme == "" || scheme == "file" || scheme.length() == 1) return false;
Chris@342:     return true;
Chris@304: }
Chris@304: 
Chris@304: bool
Chris@317: FileSource::canHandleScheme(QUrl url)
Chris@208: {
Chris@342:     // Note that a "scheme" with length 1 is probably a DOS drive letter
Chris@208:     QString scheme = url.scheme().toLower();
Chris@316:     return (scheme == "http" || scheme == "ftp" ||
Chris@342:             scheme == "file" || scheme == "" || scheme.length() == 1);
Chris@208: }
Chris@208: 
Chris@210: bool
Chris@317: FileSource::isAvailable()
Chris@210: {
Chris@316:     waitForStatus();
Chris@211:     bool available = true;
Chris@211:     if (!m_ok) available = false;
Chris@211:     else available = (m_lastStatus / 100 == 2);
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::isAvailable: " << (available ? "yes" : "no")
Chris@687:               << endl;
Chris@327: #endif
Chris@211:     return available;
Chris@210: }
Chris@210: 
Chris@208: void
Chris@317: FileSource::waitForStatus()
Chris@316: {
Chris@316:     while (m_ok && (!m_done && m_lastStatus == 0)) {
Chris@316: //        std::cerr << "waitForStatus: processing (last status " << m_lastStatus << ")" << std::endl;
Chris@392:         QCoreApplication::processEvents();
Chris@316:     }
Chris@316: }
Chris@316: 
Chris@316: void
Chris@317: FileSource::waitForData()
Chris@208: {
Chris@211:     while (m_ok && !m_done) {
Chris@690: //        SVDEBUG << "FileSource::waitForData: calling QApplication::processEvents" << endl;
Chris@392:         QCoreApplication::processEvents();
Chris@497:         usleep(10000);
Chris@208:     }
Chris@208: }
Chris@208: 
Chris@316: void
Chris@317: FileSource::setLeaveLocalFile(bool leave)
Chris@316: {
Chris@316:     m_leaveLocalFile = leave;
Chris@316: }
Chris@316: 
Chris@208: bool
Chris@317: FileSource::isOK() const
Chris@208: {
Chris@208:     return m_ok;
Chris@208: }
Chris@208: 
Chris@208: bool
Chris@317: FileSource::isDone() const
Chris@208: {
Chris@208:     return m_done;
Chris@208: }
Chris@208: 
Chris@316: bool
Chris@317: FileSource::isRemote() const
Chris@316: {
Chris@316:     return m_remote;
Chris@316: }
Chris@316: 
Chris@316: QString
Chris@317: FileSource::getLocation() const
Chris@316: {
Chris@316:     return m_url.toString();
Chris@316: }
Chris@316: 
Chris@208: QString
Chris@317: FileSource::getLocalFilename() const
Chris@208: {
Chris@208:     return m_localFilename;
Chris@208: }
Chris@208: 
Chris@208: QString
Chris@678: FileSource::getBasename() const
Chris@678: {
Chris@678:     return QFileInfo(m_localFilename).fileName();
Chris@678: }
Chris@678: 
Chris@678: QString
Chris@317: FileSource::getContentType() const
Chris@316: {
Chris@316:     return m_contentType;
Chris@316: }
Chris@316: 
Chris@316: QString
Chris@317: FileSource::getExtension() const
Chris@316: {
Chris@316:     if (m_localFilename != "") {
Chris@316:         return QFileInfo(m_localFilename).suffix().toLower();
Chris@316:     } else {
Chris@316:         return QFileInfo(m_url.toLocalFile()).suffix().toLower();
Chris@316:     }
Chris@316: }
Chris@316: 
Chris@316: QString
Chris@317: FileSource::getErrorString() const
Chris@208: {
Chris@208:     return m_errorString;
Chris@208: }
Chris@208: 
Chris@208: void
Chris@317: FileSource::dataReadProgress(int done, int total)
Chris@208: {
Chris@208:     dataTransferProgress(done, total);
Chris@208: }
Chris@208: 
Chris@208: void
Chris@317: FileSource::httpResponseHeaderReceived(const QHttpResponseHeader &resp)
Chris@210: {
Chris@520: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::httpResponseHeaderReceived" << endl;
Chris@520: #endif
Chris@497: 
Chris@497:     if (resp.statusCode() / 100 == 3) {
Chris@496:         QString location = resp.value("Location");
Chris@496: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::responseHeaderReceived: redirect to \""
Chris@687:                   << location << "\" received" << endl;
Chris@496: #endif
Chris@496:         if (location != "") {
Chris@496:             QUrl newUrl(location);
Chris@496:             if (newUrl != m_url) {
Chris@497:                 cleanup();
Chris@497:                 deleteCacheFile();
Chris@529: #ifdef DEBUG_FILE_SOURCE
Chris@529:                 decCount(m_url.toString());
Chris@529:                 incCount(newUrl.toString());
Chris@529: #endif
Chris@496:                 m_url = newUrl;
Chris@497:                 m_localFile = 0;
Chris@496:                 m_lastStatus = 0;
Chris@497:                 m_done = false;
Chris@497:                 m_refCounted = false;
Chris@496:                 init();
Chris@496:                 return;
Chris@496:             }
Chris@496:         }
Chris@496:     }
Chris@497: 
Chris@497:     m_lastStatus = resp.statusCode();
Chris@210:     if (m_lastStatus / 100 >= 4) {
Chris@210:         m_errorString = QString("%1 %2")
Chris@210:             .arg(resp.statusCode()).arg(resp.reasonPhrase());
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::responseHeaderReceived: "
Chris@687:                   << m_errorString << endl;
Chris@327: #endif
Chris@211:     } else {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::responseHeaderReceived: "
Chris@687:                   << m_lastStatus << endl;
Chris@327: #endif
Chris@315:         if (resp.hasContentType()) m_contentType = resp.contentType();
Chris@325:     }
Chris@325:     emit statusAvailable();
Chris@210: }
Chris@210: 
Chris@210: void
Chris@317: FileSource::ftpCommandFinished(int id, bool error)
Chris@214: {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@317:     std::cerr << "FileSource::ftpCommandFinished(" << id << ", " << error << ")" << std::endl;
Chris@327: #endif
Chris@214: 
Chris@214:     if (!m_ftp) return;
Chris@214: 
Chris@214:     QFtp::Command command = m_ftp->currentCommand();
Chris@214: 
Chris@214:     if (!error) {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::ftpCommandFinished: success for command "
Chris@687:                   << command << endl;
Chris@327: #endif
Chris@214:         return;
Chris@214:     }
Chris@214: 
Chris@214:     if (command == QFtp::ConnectToHost) {
Chris@214:         m_errorString = tr("Failed to connect to FTP server");
Chris@214:     } else if (command == QFtp::Login) {
Chris@214:         m_errorString = tr("Login failed");
Chris@214:     } else if (command == QFtp::Cd) {
Chris@214:         m_errorString = tr("Failed to change to correct directory");
Chris@214:     } else if (command == QFtp::Get) {
Chris@214:         m_errorString = tr("FTP download aborted");
Chris@214:     }
Chris@214: 
Chris@214:     m_lastStatus = 400; // for done()
Chris@214: }
Chris@214: 
Chris@214: void
Chris@317: FileSource::dataTransferProgress(qint64 done, qint64 total)
Chris@208: {
Chris@208:     int percent = int((double(done) / double(total)) * 100.0 - 0.1);
Chris@208:     emit progress(percent);
Chris@210: }
Chris@210: 
Chris@210: void
Chris@317: FileSource::cancelled()
Chris@210: {
Chris@210:     m_done = true;
Chris@316:     cleanup();
Chris@316: 
Chris@210:     m_ok = false;
Chris@210:     m_errorString = tr("Download cancelled");
Chris@208: }
Chris@208: 
Chris@208: void
Chris@317: FileSource::done(bool error)
Chris@208: {
Chris@327:     emit progress(100);
Chris@327: 
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@317:     std::cerr << "FileSource::done(" << error << ")" << std::endl;
Chris@327: #endif
Chris@211: 
Chris@211:     if (m_done) return;
Chris@211: 
Chris@208:     if (error) {
Chris@208:         if (m_http) {
Chris@208:             m_errorString = m_http->errorString();
Chris@208:         } else if (m_ftp) {
Chris@208:             m_errorString = m_ftp->errorString();
Chris@208:         }
Chris@208:     }
Chris@208: 
Chris@210:     if (m_lastStatus / 100 >= 4) {
Chris@211:         error = true;
Chris@210:     }
Chris@210: 
Chris@211:     cleanup();
Chris@208: 
Chris@211:     if (!error) {
Chris@208:         QFileInfo fi(m_localFilename);
Chris@208:         if (!fi.exists()) {
Chris@208:             m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
Chris@211:             error = true;
Chris@208:         } else if (fi.size() == 0) {
Chris@208:             m_errorString = tr("File contains no data!");
Chris@211:             error = true;
Chris@208:         }
Chris@208:     }
Chris@211: 
Chris@211:     if (error) {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@469:         std::cerr << "FileSource::done: error is " << error << ", deleting cache file" << std::endl;
Chris@327: #endif
Chris@316:         deleteCacheFile();
Chris@211:     }
Chris@211: 
Chris@211:     m_ok = !error;
Chris@520:     if (m_localFile) m_localFile->flush();
Chris@211:     m_done = true;
Chris@304:     emit ready();
Chris@211: }
Chris@211: 
Chris@211: void
Chris@317: FileSource::deleteCacheFile()
Chris@211: {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::deleteCacheFile(\"" << m_localFilename << "\")" << endl;
Chris@327: #endif
Chris@211: 
Chris@211:     cleanup();
Chris@211: 
Chris@316:     if (m_localFilename == "") {
Chris@316:         return;
Chris@316:     }
Chris@211: 
Chris@316:     if (!isRemote()) {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@316:         std::cerr << "not a cache file" << std::endl;
Chris@327: #endif
Chris@316:         return;
Chris@316:     }
Chris@316: 
Chris@316:     if (m_refCounted) {
Chris@304: 
Chris@304:         QMutexLocker locker(&m_mapMutex);
Chris@316:         m_refCounted = false;
Chris@304: 
Chris@304:         if (m_refCountMap[m_url] > 0) {
Chris@304:             m_refCountMap[m_url]--;
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@316:             std::cerr << "reduced ref count to " << m_refCountMap[m_url] << std::endl;
Chris@327: #endif
Chris@304:             if (m_refCountMap[m_url] > 0) {
Chris@304:                 m_done = true;
Chris@304:                 return;
Chris@304:             }
Chris@304:         }
Chris@304:     }
Chris@304: 
Chris@211:     m_fileCreationMutex.lock();
Chris@211: 
Chris@211:     if (!QFile(m_localFilename).remove()) {
Chris@469: #ifdef DEBUG_FILE_SOURCE
Chris@686:         std::cerr << "FileSource::deleteCacheFile: ERROR: Failed to delete file \"" << m_localFilename << "\"" << std::endl;
Chris@469: #endif
Chris@211:     } else {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::deleteCacheFile: Deleted cache file \"" << m_localFilename << "\"" << endl;
Chris@327: #endif
Chris@211:         m_localFilename = "";
Chris@211:     }
Chris@211: 
Chris@211:     m_fileCreationMutex.unlock();
Chris@211: 
Chris@208:     m_done = true;
Chris@208: }
Chris@208: 
Chris@316: bool
Chris@317: FileSource::createCacheFile()
Chris@208: {
Chris@316:     {
Chris@316:         QMutexLocker locker(&m_mapMutex);
Chris@316: 
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:         SVDEBUG << "FileSource::createCacheFile: refcount is " << m_refCountMap[m_url] << endl;
Chris@327: #endif
Chris@316: 
Chris@316:         if (m_refCountMap[m_url] > 0) {
Chris@316:             m_refCountMap[m_url]++;
Chris@316:             m_localFilename = m_remoteLocalMap[m_url];
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@316:             std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl;
Chris@327: #endif
Chris@316:             m_refCounted = true;
Chris@316:             return true;
Chris@316:         }
Chris@316:     }
Chris@316: 
Chris@208:     QDir dir;
Chris@208:     try {
Chris@208:         dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
Chris@208:     } catch (DirectoryCreationFailed f) {
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@317:         std::cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl;
Chris@327: #endif
Chris@208:         return "";
Chris@208:     }
Chris@208: 
Chris@316:     QString filepart = m_url.path().section('/', -1, -1,
Chris@316:                                             QString::SectionSkipEmpty);
Chris@208: 
Chris@457:     QString extension = "";
Chris@457:     if (filepart.contains('.')) extension = filepart.section('.', -1);
Chris@457: 
Chris@208:     QString base = filepart;
Chris@208:     if (extension != "") {
Chris@208:         base = base.left(base.length() - extension.length() - 1);
Chris@208:     }
Chris@208:     if (base == "") base = "remote";
Chris@208: 
Chris@208:     QString filename;
Chris@208: 
Chris@208:     if (extension == "") {
Chris@208:         filename = base;
Chris@208:     } else {
Chris@208:         filename = QString("%1.%2").arg(base).arg(extension);
Chris@208:     }
Chris@208: 
Chris@208:     QString filepath(dir.filePath(filename));
Chris@208: 
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::createCacheFile: URL is \"" << m_url.toString() << "\", dir is \"" << dir.path() << "\", base \"" << base << "\", extension \"" << extension << "\", filebase \"" << filename << "\", filename \"" << filepath << "\"" << endl;
Chris@327: #endif
Chris@208: 
Chris@316:     QMutexLocker fcLocker(&m_fileCreationMutex);
Chris@316: 
Chris@208:     ++m_count;
Chris@208: 
Chris@208:     if (QFileInfo(filepath).exists() ||
Chris@208:         !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208: 
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@317:         std::cerr << "FileSource::createCacheFile: Failed to create local file \""
Chris@686:                   << filepath << "\" for URL \""
Chris@686:                   << m_url.toString() << "\" (or file already exists): appending suffix instead" << std::endl;
Chris@327: #endif
Chris@208: 
Chris@208:         if (extension == "") {
Chris@208:             filename = QString("%1_%2").arg(base).arg(m_count);
Chris@208:         } else {
Chris@208:             filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
Chris@208:         }
Chris@208:         filepath = dir.filePath(filename);
Chris@208: 
Chris@208:         if (QFileInfo(filepath).exists() ||
Chris@208:             !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208: 
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@317:             std::cerr << "FileSource::createCacheFile: ERROR: Failed to create local file \""
Chris@686:                       << filepath << "\" for URL \""
Chris@686:                       << m_url.toString() << "\" (or file already exists)" << std::endl;
Chris@327: #endif
Chris@208: 
Chris@208:             return "";
Chris@208:         }
Chris@208:     }
Chris@208: 
Chris@327: #ifdef DEBUG_FILE_SOURCE
Chris@690:     SVDEBUG << "FileSource::createCacheFile: url "
Chris@686:               << m_url.toString() << " -> local filename "
Chris@687:               << filepath << endl;
Chris@327: #endif
Chris@316:     
Chris@316:     m_localFilename = filepath;
Chris@208: 
Chris@316:     return false;
Chris@208: }
Chris@327: