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@208: #include "RemoteFile.h" Chris@208: #include "base/TempDirectory.h" Chris@208: #include "base/Exceptions.h" Chris@208: Chris@208: #include Chris@208: #include Chris@208: #include Chris@208: #include Chris@208: #include Chris@208: #include Chris@210: #include Chris@208: Chris@208: #include Chris@208: Chris@208: int Chris@208: RemoteFile::m_count = 0; Chris@208: Chris@208: QMutex Chris@208: RemoteFile::m_fileCreationMutex; Chris@208: Chris@208: RemoteFile::RemoteFile(QUrl 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@208: m_done(false), Chris@210: m_progressDialog(0), Chris@210: m_progressShowTimer(this) Chris@208: { Chris@208: if (!canHandleScheme(url)) { Chris@208: std::cerr << "RemoteFile::RemoteFile: ERROR: Unsupported scheme in URL \"" << url.toString().toStdString() << "\"" << std::endl; Chris@208: return; Chris@208: } Chris@208: Chris@208: m_localFilename = createLocalFile(url); Chris@208: if (m_localFilename == "") return; Chris@208: m_localFile = new QFile(m_localFilename); Chris@208: m_localFile->open(QFile::WriteOnly); Chris@208: Chris@208: QString scheme = url.scheme().toLower(); Chris@208: Chris@208: if (scheme == "http") { Chris@208: Chris@208: m_http = new QHttp(url.host(), url.port(80)); Chris@208: connect(m_http, SIGNAL(done(bool)), this, SLOT(done(bool))); Chris@208: connect(m_http, SIGNAL(dataReadProgress(int, int)), Chris@208: this, SLOT(dataReadProgress(int, int))); Chris@210: connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), Chris@210: this, SLOT(responseHeaderReceived(const QHttpResponseHeader &))); Chris@208: m_http->get(url.path(), m_localFile); Chris@208: m_ok = true; Chris@208: Chris@208: } else if (scheme == "ftp") { Chris@208: Chris@208: m_ftp = new QFtp; Chris@208: connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(bool))); Chris@208: connect(m_ftp, SIGNAL(dataTransferProgress(qint64, qint64)), Chris@208: this, SLOT(dataTransferProgress(qint64, qint64))); Chris@208: m_ftp->connectToHost(url.host(), url.port(21)); Chris@208: Chris@208: QString username = url.userName(); Chris@208: if (username == "") { Chris@208: username = "anonymous"; Chris@208: } Chris@208: Chris@208: QString password = url.password(); Chris@208: if (password == "") { Chris@208: password = QString("%1@%2").arg(getenv("USER")).arg(getenv("HOST")); Chris@208: } Chris@208: Chris@208: QStringList path = url.path().split('/'); Chris@208: for (QStringList::iterator i = path.begin(); i != path.end(); ) { Chris@208: QString bit = *i; Chris@208: ++i; Chris@208: if (i != path.end()) { Chris@208: m_ftp->cd(*i); Chris@208: } else { Chris@208: m_ftp->get(*i, m_localFile); Chris@208: } Chris@208: } Chris@208: Chris@208: m_ok = true; Chris@208: } Chris@208: Chris@208: if (m_ok) { Chris@208: m_progressDialog = new QProgressDialog(tr("Downloading %1...").arg(url.toString()), tr("Cancel"), 0, 100); Chris@210: m_progressDialog->hide(); Chris@210: connect(&m_progressShowTimer, SIGNAL(timeout()), Chris@210: this, SLOT(showProgressDialog())); Chris@210: connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(cancelled())); Chris@210: m_progressShowTimer.setSingleShot(true); Chris@210: m_progressShowTimer.start(2000); Chris@208: } Chris@208: } Chris@208: Chris@208: RemoteFile::~RemoteFile() Chris@208: { Chris@208: delete m_ftp; Chris@208: delete m_http; Chris@208: delete m_localFile; Chris@208: delete m_progressDialog; Chris@208: } Chris@208: Chris@208: bool Chris@208: RemoteFile::canHandleScheme(QUrl url) Chris@208: { Chris@208: QString scheme = url.scheme().toLower(); Chris@208: return (scheme == "http" || scheme == "ftp"); Chris@208: } Chris@208: Chris@210: bool Chris@210: RemoteFile::isAvailable() Chris@210: { Chris@210: while (!m_done && m_lastStatus == 0) { Chris@210: QApplication::processEvents(); Chris@210: } Chris@210: return (m_lastStatus / 100 == 2); Chris@210: } Chris@210: Chris@208: void Chris@208: RemoteFile::wait() Chris@208: { Chris@208: while (!m_done) { Chris@208: QApplication::processEvents(); Chris@208: } Chris@208: } Chris@208: Chris@208: bool Chris@208: RemoteFile::isOK() const Chris@208: { Chris@208: return m_ok; Chris@208: } Chris@208: Chris@208: bool Chris@208: RemoteFile::isDone() const Chris@208: { Chris@208: return m_done; Chris@208: } Chris@208: Chris@208: QString Chris@208: RemoteFile::getLocalFilename() const Chris@208: { Chris@208: return m_localFilename; Chris@208: } Chris@208: Chris@208: QString Chris@208: RemoteFile::getErrorString() const Chris@208: { Chris@208: return m_errorString; Chris@208: } Chris@208: Chris@208: void Chris@208: RemoteFile::dataReadProgress(int done, int total) Chris@208: { Chris@208: dataTransferProgress(done, total); Chris@208: } Chris@208: Chris@208: void Chris@210: RemoteFile::responseHeaderReceived(const QHttpResponseHeader &resp) Chris@210: { Chris@210: 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@210: } Chris@210: } Chris@210: Chris@210: void Chris@208: RemoteFile::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@208: Chris@208: m_progressDialog->setValue(percent); Chris@210: m_progressDialog->show(); Chris@210: } Chris@210: Chris@210: void Chris@210: RemoteFile::cancelled() Chris@210: { Chris@210: delete m_http; Chris@210: m_http = 0; Chris@210: delete m_ftp; Chris@210: m_ftp = 0; Chris@210: delete m_progressDialog; Chris@210: m_progressDialog = 0; Chris@210: delete m_localFile; Chris@210: m_localFile = 0; Chris@210: m_done = true; Chris@210: m_ok = false; Chris@210: m_errorString = tr("Download cancelled"); Chris@208: } Chris@208: Chris@208: void Chris@208: RemoteFile::done(bool error) Chris@208: { Chris@208: emit progress(100); Chris@208: m_ok = !error; Chris@210: 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@210: m_ok = false; Chris@210: } Chris@210: Chris@208: delete m_localFile; Chris@208: m_localFile = 0; Chris@208: Chris@208: delete m_progressDialog; Chris@208: m_progressDialog = 0; Chris@208: Chris@208: if (m_ok) { 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@208: m_ok = false; Chris@208: } else if (fi.size() == 0) { Chris@208: m_errorString = tr("File contains no data!"); Chris@208: m_ok = false; Chris@208: } Chris@208: } Chris@208: m_done = true; Chris@208: } Chris@208: Chris@210: void Chris@210: RemoteFile::showProgressDialog() Chris@210: { Chris@210: if (m_progressDialog) m_progressDialog->show(); Chris@210: } Chris@210: Chris@208: QString Chris@208: RemoteFile::createLocalFile(QUrl url) Chris@208: { Chris@208: //!!! should we actually put up dialogs for these errors? or propagate an exception? Chris@208: Chris@208: QDir dir; Chris@208: try { Chris@208: dir = TempDirectory::getInstance()->getSubDirectoryPath("download"); Chris@208: } catch (DirectoryCreationFailed f) { Chris@208: std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl; Chris@208: return ""; Chris@208: } Chris@208: Chris@208: QString filepart = url.path().section('/', -1, -1, Chris@208: QString::SectionSkipEmpty); Chris@208: Chris@208: QString extension = filepart.section('.', -1); 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@208: 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; Chris@208: Chris@208: m_fileCreationMutex.lock(); Chris@208: ++m_count; Chris@208: Chris@208: if (QFileInfo(filepath).exists() || Chris@208: !QFile(filepath).open(QFile::WriteOnly)) { Chris@208: Chris@208: std::cerr << "RemoteFile::createLocalFile: Failed to create local file \"" Chris@208: << filepath.toStdString() << "\" for URL \"" Chris@208: << url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl; Chris@208: 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@208: std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create local file \"" Chris@208: << filepath.toStdString() << "\" for URL \"" Chris@208: << url.toString().toStdString() << "\" (or file already exists)" << std::endl; Chris@208: Chris@208: m_fileCreationMutex.unlock(); Chris@208: return ""; Chris@208: } Chris@208: } Chris@208: Chris@208: m_fileCreationMutex.unlock(); Chris@208: Chris@208: std::cerr << "RemoteFile::createLocalFile: url " Chris@208: << url.toString().toStdString() << " -> local filename " Chris@208: << filepath.toStdString() << std::endl; Chris@208: Chris@208: return filepath; Chris@208: }