Mercurial > hg > svcore
view data/fileio/RemoteFile.cpp @ 304:4fc6f49436b3
* Add support for remote files to image layer
author | Chris Cannam |
---|---|
date | Fri, 05 Oct 2007 15:52:52 +0000 |
parents | 557e00480279 |
children | 96ef9746c560 |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. This file copyright 2007 QMUL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "RemoteFile.h" #include "base/TempDirectory.h" #include "base/Exceptions.h" #include <QHttp> #include <QFtp> #include <QFileInfo> #include <QDir> #include <QApplication> #include <QProgressDialog> #include <QHttpResponseHeader> #include <iostream> int RemoteFile::m_count = 0; QMutex RemoteFile::m_fileCreationMutex; RemoteFile::RemoteRefCountMap RemoteFile::m_refCountMap; RemoteFile::RemoteLocalMap RemoteFile::m_remoteLocalMap; QMutex RemoteFile::m_mapMutex; RemoteFile::RemoteFile(QUrl url) : m_url(url), m_ftp(0), m_http(0), m_localFile(0), m_ok(false), m_lastStatus(0), m_done(false), m_progressDialog(0), m_progressShowTimer(this), m_referenced(false) { if (!canHandleScheme(url)) { std::cerr << "RemoteFile::RemoteFile: ERROR: Unsupported scheme in URL \"" << url.toString().toStdString() << "\"" << std::endl; return; } QMutexLocker locker(&m_mapMutex); std::cerr << "RemoteFile::RemoteFile: refcount is " << m_refCountMap[m_url] << std::endl; if (m_refCountMap[m_url] > 0) { m_refCountMap[m_url]++; m_localFilename = m_remoteLocalMap[m_url]; std::cerr << "raising it" << std::endl; m_ok = true; m_done = true; m_referenced = true; return; } m_localFilename = createLocalFile(url); if (m_localFilename == "") return; m_localFile = new QFile(m_localFilename); m_localFile->open(QFile::WriteOnly); QString scheme = url.scheme().toLower(); if (scheme == "http") { m_ok = true; m_http = new QHttp(url.host(), url.port(80)); connect(m_http, SIGNAL(done(bool)), this, SLOT(done(bool))); connect(m_http, SIGNAL(dataReadProgress(int, int)), this, SLOT(dataReadProgress(int, int))); connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), this, SLOT(httpResponseHeaderReceived(const QHttpResponseHeader &))); // I don't quite understand this. url.path() returns a path // without percent encoding; for example, spaces appear as // literal spaces. This generally won't work if sent to the // server directly. You can retrieve a correctly encoded URL // from QUrl using url.toEncoded(), but that gives you the // whole URL; there doesn't seem to be any way to retrieve // only an encoded path. Furthermore there doesn't seem to be // any way to convert a retrieved path into an encoded path // without explicitly specifying that you don't want the path // separators ("/") to be encoded. (Besides being painful to // manage, I don't see how this can work correctly in any case // where a percent-encoded "/" is supposed to appear within a // path element?) There also seems to be no way to retrieve // the path plus query string, i.e. everything that I need to // send to the HTTP server. And no way for QHttp to take a // QUrl argument. I'm obviously missing something. // So, two ways to do this: query the bits from the URL, // encode them individually, and glue them back together // again... /* QString path = QUrl::toPercentEncoding(url.path(), "/"); QList<QPair<QString, QString> > query = url.queryItems(); if (!query.empty()) { QStringList q2; for (QList<QPair<QString, QString> >::iterator i = query.begin(); i != query.end(); ++i) { q2.push_back(QString("%1=%3") .arg(QString(QUrl::toPercentEncoding(i->first))) .arg(QString(QUrl::toPercentEncoding(i->second)))); } path = QString("%1%2%3") .arg(path).arg("?") .arg(q2.join("&")); } */ // ...or, much simpler but relying on knowledge about the // scheme://host/path/path/query etc format of the URL, we can // get the whole URL ready-encoded and then split it on "/" as // appropriate... QString path = "/" + QString(url.toEncoded()).section('/', 3); std::cerr << "RemoteFile: path is \"" << path.toStdString() << "\"" << std::endl; m_http->get(path, m_localFile); } else if (scheme == "ftp") { m_ok = true; m_ftp = new QFtp; connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(bool))); connect(m_ftp, SIGNAL(commandFinished(int, bool)), this, SLOT(ftpCommandFinished(int, bool))); connect(m_ftp, SIGNAL(dataTransferProgress(qint64, qint64)), this, SLOT(dataTransferProgress(qint64, qint64))); m_ftp->connectToHost(url.host(), url.port(21)); QString username = url.userName(); if (username == "") { username = "anonymous"; } QString password = url.password(); if (password == "") { password = QString("%1@%2").arg(getenv("USER")).arg(getenv("HOST")); } m_ftp->login(username, password); QString dirpath = url.path().section('/', 0, -2); QString filename = url.path().section('/', -1); if (dirpath == "") dirpath = "/"; m_ftp->cd(dirpath); m_ftp->get(filename, m_localFile); } if (m_ok) { m_remoteLocalMap[m_url] = m_localFilename; m_refCountMap[m_url]++; m_referenced = true; m_progressDialog = new QProgressDialog(tr("Downloading %1...").arg(url.toString()), tr("Cancel"), 0, 100); m_progressDialog->hide(); connect(&m_progressShowTimer, SIGNAL(timeout()), this, SLOT(showProgressDialog())); connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(cancelled())); m_progressShowTimer.setSingleShot(true); m_progressShowTimer.start(2000); } } RemoteFile::~RemoteFile() { cleanup(); } void RemoteFile::cleanup() { m_done = true; if (m_http) { QHttp *h = m_http; m_http = 0; h->abort(); h->deleteLater(); } if (m_ftp) { QFtp *f = m_ftp; m_ftp = 0; f->abort(); f->deleteLater(); } delete m_progressDialog; m_progressDialog = 0; delete m_localFile; // does not actually delete the file m_localFile = 0; } bool RemoteFile::isRemote(QString fileOrUrl) { return (fileOrUrl.startsWith("http:") || fileOrUrl.startsWith("ftp:")); } bool RemoteFile::canHandleScheme(QUrl url) { QString scheme = url.scheme().toLower(); return (scheme == "http" || scheme == "ftp"); } bool RemoteFile::isAvailable() { while (m_ok && (!m_done && m_lastStatus == 0)) { QApplication::processEvents(); } bool available = true; if (!m_ok) available = false; else available = (m_lastStatus / 100 == 2); std::cerr << "RemoteFile::isAvailable: " << (available ? "yes" : "no") << std::endl; return available; } void RemoteFile::wait() { while (m_ok && !m_done) { QApplication::processEvents(); } } bool RemoteFile::isOK() const { return m_ok; } bool RemoteFile::isDone() const { return m_done; } QString RemoteFile::getLocalFilename() const { return m_localFilename; } QString RemoteFile::getErrorString() const { return m_errorString; } void RemoteFile::dataReadProgress(int done, int total) { dataTransferProgress(done, total); } void RemoteFile::httpResponseHeaderReceived(const QHttpResponseHeader &resp) { m_lastStatus = resp.statusCode(); if (m_lastStatus / 100 >= 4) { m_errorString = QString("%1 %2") .arg(resp.statusCode()).arg(resp.reasonPhrase()); std::cerr << "RemoteFile::responseHeaderReceived: " << m_errorString.toStdString() << std::endl; } else { std::cerr << "RemoteFile::responseHeaderReceived: " << m_lastStatus << std::endl; } } void RemoteFile::ftpCommandFinished(int id, bool error) { std::cerr << "RemoteFile::ftpCommandFinished(" << id << ", " << error << ")" << std::endl; if (!m_ftp) return; QFtp::Command command = m_ftp->currentCommand(); if (!error) { std::cerr << "RemoteFile::ftpCommandFinished: success for command " << command << std::endl; return; } if (command == QFtp::ConnectToHost) { m_errorString = tr("Failed to connect to FTP server"); } else if (command == QFtp::Login) { m_errorString = tr("Login failed"); } else if (command == QFtp::Cd) { m_errorString = tr("Failed to change to correct directory"); } else if (command == QFtp::Get) { m_errorString = tr("FTP download aborted"); } m_lastStatus = 400; // for done() } void RemoteFile::dataTransferProgress(qint64 done, qint64 total) { if (!m_progressDialog) return; int percent = int((double(done) / double(total)) * 100.0 - 0.1); emit progress(percent); if (percent > 0) { m_progressDialog->setValue(percent); m_progressDialog->show(); } } void RemoteFile::cancelled() { deleteLocalFile(); m_done = true; m_ok = false; m_errorString = tr("Download cancelled"); } void RemoteFile::done(bool error) { std::cerr << "RemoteFile::done(" << error << ")" << std::endl; if (m_done) return; emit progress(100); if (error) { if (m_http) { m_errorString = m_http->errorString(); } else if (m_ftp) { m_errorString = m_ftp->errorString(); } } if (m_lastStatus / 100 >= 4) { error = true; } cleanup(); if (!error) { QFileInfo fi(m_localFilename); if (!fi.exists()) { m_errorString = tr("Failed to create local file %1").arg(m_localFilename); error = true; } else if (fi.size() == 0) { m_errorString = tr("File contains no data!"); error = true; } } if (error) { deleteLocalFile(); } m_ok = !error; m_done = true; emit ready(); } void RemoteFile::deleteLocalFile() { // std::cerr << "RemoteFile::deleteLocalFile" << std::endl; cleanup(); if (m_localFilename == "") return; if (m_referenced) { QMutexLocker locker(&m_mapMutex); m_referenced = false; if (m_refCountMap[m_url] > 0) { m_refCountMap[m_url]--; if (m_refCountMap[m_url] > 0) { m_done = true; return; } } } m_fileCreationMutex.lock(); if (!QFile(m_localFilename).remove()) { std::cerr << "RemoteFile::deleteLocalFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl; } else { m_localFilename = ""; } m_fileCreationMutex.unlock(); m_done = true; } void RemoteFile::showProgressDialog() { if (m_progressDialog) m_progressDialog->show(); } QString RemoteFile::createLocalFile(QUrl url) { QDir dir; try { dir = TempDirectory::getInstance()->getSubDirectoryPath("download"); } catch (DirectoryCreationFailed f) { std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl; return ""; } QString filepart = url.path().section('/', -1, -1, QString::SectionSkipEmpty); QString extension = filepart.section('.', -1); QString base = filepart; if (extension != "") { base = base.left(base.length() - extension.length() - 1); } if (base == "") base = "remote"; QString filename; if (extension == "") { filename = base; } else { filename = QString("%1.%2").arg(base).arg(extension); } QString filepath(dir.filePath(filename)); std::cerr << "RemoteFile::createLocalFile: URL is \"" << url.toString().toStdString() << "\", dir is \"" << dir.path().toStdString() << "\", base \"" << base.toStdString() << "\", extension \"" << extension.toStdString() << "\", filebase \"" << filename.toStdString() << "\", filename \"" << filepath.toStdString() << "\"" << std::endl; m_fileCreationMutex.lock(); ++m_count; if (QFileInfo(filepath).exists() || !QFile(filepath).open(QFile::WriteOnly)) { std::cerr << "RemoteFile::createLocalFile: Failed to create local file \"" << filepath.toStdString() << "\" for URL \"" << url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl; if (extension == "") { filename = QString("%1_%2").arg(base).arg(m_count); } else { filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension); } filepath = dir.filePath(filename); if (QFileInfo(filepath).exists() || !QFile(filepath).open(QFile::WriteOnly)) { std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create local file \"" << filepath.toStdString() << "\" for URL \"" << url.toString().toStdString() << "\" (or file already exists)" << std::endl; m_fileCreationMutex.unlock(); return ""; } } m_fileCreationMutex.unlock(); std::cerr << "RemoteFile::createLocalFile: url " << url.toString().toStdString() << " -> local filename " << filepath.toStdString() << std::endl; return filepath; }