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 Chris@208: #include Chris@208: #include Chris@208: #include Chris@392: #include Chris@210: #include Chris@208: Chris@208: #include Chris@405: #include Chris@208: Chris@608: #include 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 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@661: std::cerr << "FileSource::FileSource(" << fileOrUrl.toStdString() << "): url <" << m_url.toString().toStdString() << ">" << std::endl; Chris@529: incCount(m_url.toString()); Chris@327: #endif Chris@316: Chris@316: if (!canHandleScheme(m_url)) { Chris@317: std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::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@616: std::cerr << "FileSource::FileSource: Failed to open local file with URL \"" << m_url.toString().toStdString() << "; 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@497: std::cerr << "FileSource::FileSource(string) exiting" << std::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@317: std::cerr << "FileSource::FileSource(" << url.toString().toStdString() << ") [as url]" << std::endl; Chris@529: incCount(m_url.toString()); Chris@327: #endif Chris@316: Chris@316: if (!canHandleScheme(m_url)) { Chris@317: std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::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@497: std::cerr << "FileSource::FileSource(url) exiting" << std::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@317: std::cerr << "FileSource::FileSource(" << m_url.toString().toStdString() << ") [copy ctor]" << std::endl; Chris@529: incCount(m_url.toString()); Chris@327: #endif Chris@304: Chris@316: if (!canHandleScheme(m_url)) { Chris@317: std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::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@317: std::cerr << "FileSource::FileSource(copy ctor): ref count is " Chris@316: << m_refCountMap[m_url] << std::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@527: std::cerr << "FileSource::FileSource(" << m_url.toString().toStdString() << ") [copy ctor]: note: local filename is \"" << m_localFilename.toStdString() << "\"" << std::endl; Chris@527: #endif Chris@527: Chris@527: #ifdef DEBUG_FILE_SOURCE Chris@497: std::cerr << "FileSource::FileSource(copy ctor) exiting" << std::endl; Chris@504: #endif Chris@316: } Chris@316: Chris@317: FileSource::~FileSource() Chris@316: { Chris@327: #ifdef DEBUG_FILE_SOURCE Chris@317: std::cerr << "FileSource(" << m_url.toString().toStdString() << ")::~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@355: std::cerr << "FileSource::init: Not a remote URL" << std::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@355: std::cerr << "FileSource::init: URL translates to local filename \"" Chris@661: << m_localFilename.toStdString() << "\" (with literal=" << literal << ")" << std::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@614: std::cerr << "FileSource::init: Local file of this name does not exist, trying URL as a literal filename" << std::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@469: std::cerr << "FileSource::init: Already have this one" << std::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@317: std::cerr << "FileSource::init: Don't have local copy of \"" Chris@469: << m_url.toString().toStdString() << "\", retrieving" << std::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@317: std::cerr << "FileSource::init: Another FileSource has got there first, abandoning our download and using theirs" << std::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 > query = m_url.queryItems(); Chris@316: if (!query.empty()) { Chris@316: QStringList q2; Chris@316: for (QList >::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@317: std::cerr << "FileSource: path is \"" Chris@316: << path.toStdString() << "\"" << std::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@520: << m_preferredContentType.toStdString() << "\"" << 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@317: std::cerr << "FileSource::isAvailable: " << (available ? "yes" : "no") Chris@211: << std::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@357: // std::cerr << "FileSource::waitForData: calling QApplication::processEvents" << std::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@497: std::cerr << "FileSource::httpResponseHeaderReceived" << std::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@496: std::cerr << "FileSource::responseHeaderReceived: redirect to \"" Chris@496: << location.toStdString() << "\" received" << std::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@317: std::cerr << "FileSource::responseHeaderReceived: " Chris@211: << m_errorString.toStdString() << std::endl; Chris@327: #endif Chris@211: } else { Chris@327: #ifdef DEBUG_FILE_SOURCE Chris@317: std::cerr << "FileSource::responseHeaderReceived: " Chris@211: << m_lastStatus << std::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@317: std::cerr << "FileSource::ftpCommandFinished: success for command " Chris@214: << command << std::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@317: std::cerr << "FileSource::deleteCacheFile(\"" << m_localFilename.toStdString() << "\")" << std::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@317: std::cerr << "FileSource::deleteCacheFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl; Chris@469: #endif Chris@211: } else { Chris@327: #ifdef DEBUG_FILE_SOURCE Chris@317: std::cerr << "FileSource::deleteCacheFile: Deleted cache file \"" << m_localFilename.toStdString() << "\"" << std::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@317: std::cerr << "FileSource::createCacheFile: refcount is " << m_refCountMap[m_url] << std::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@317: std::cerr << "FileSource::createCacheFile: URL is \"" << m_url.toString().toStdString() << "\", dir is \"" << dir.path().toStdString() << "\", base \"" << base.toStdString() << "\", extension \"" << extension.toStdString() << "\", filebase \"" << filename.toStdString() << "\", filename \"" << filepath.toStdString() << "\"" << std::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@208: << filepath.toStdString() << "\" for URL \"" Chris@316: << m_url.toString().toStdString() << "\" (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@208: << filepath.toStdString() << "\" for URL \"" Chris@316: << m_url.toString().toStdString() << "\" (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@317: std::cerr << "FileSource::createCacheFile: url " Chris@316: << m_url.toString().toStdString() << " -> local filename " Chris@316: << filepath.toStdString() << std::endl; Chris@327: #endif Chris@316: Chris@316: m_localFilename = filepath; Chris@208: Chris@316: return false; Chris@208: } Chris@327: