view data/fileio/RemoteFile.cpp @ 210:a06afefe45ee

* Cancel when downloading file * Handle status codes (404 etc) * Add RemoteFile::isAvailable * Start on FileFinder for looking up files referred to in distant sessions
author Chris Cannam
date Wed, 10 Jan 2007 17:26:39 +0000
parents 8a3d68910b37
children e2bbb58e6df6
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::RemoteFile(QUrl 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)
{
    if (!canHandleScheme(url)) {
        std::cerr << "RemoteFile::RemoteFile: ERROR: Unsupported scheme in URL \"" << url.toString().toStdString() << "\"" << std::endl;
        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_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(responseHeaderReceived(const QHttpResponseHeader &)));
        m_http->get(url.path(), m_localFile);
        m_ok = true;

    } else if (scheme == "ftp") {

        m_ftp = new QFtp;
        connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(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"));
        }

        QStringList path = url.path().split('/');
        for (QStringList::iterator i = path.begin(); i != path.end(); ) {
            QString bit = *i;
            ++i;
            if (i != path.end()) {
                m_ftp->cd(*i);
            } else {
                m_ftp->get(*i, m_localFile);
            }
        }

        m_ok = true;
    }

    if (m_ok) {
        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()
{
    delete m_ftp;
    delete m_http;
    delete m_localFile;
    delete m_progressDialog;
}

bool
RemoteFile::canHandleScheme(QUrl url)
{
    QString scheme = url.scheme().toLower();
    return (scheme == "http" || scheme == "ftp");
}

bool
RemoteFile::isAvailable()
{
    while (!m_done && m_lastStatus == 0) {
        QApplication::processEvents();
    }
    return (m_lastStatus / 100 == 2);
}

void
RemoteFile::wait()
{
    while (!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::responseHeaderReceived(const QHttpResponseHeader &resp)
{
    m_lastStatus = resp.statusCode();
    if (m_lastStatus / 100 >= 4) {
        m_errorString = QString("%1 %2")
            .arg(resp.statusCode()).arg(resp.reasonPhrase());
    }
}

void
RemoteFile::dataTransferProgress(qint64 done, qint64 total)
{
    int percent = int((double(done) / double(total)) * 100.0 - 0.1);
    emit progress(percent);

    m_progressDialog->setValue(percent);
    m_progressDialog->show();
}

void
RemoteFile::cancelled()
{
    delete m_http;
    m_http = 0;
    delete m_ftp;
    m_ftp = 0;
    delete m_progressDialog;
    m_progressDialog = 0;
    delete m_localFile;
    m_localFile = 0;
    m_done = true;
    m_ok = false;
    m_errorString = tr("Download cancelled");
}

void
RemoteFile::done(bool error)
{
    emit progress(100);
    m_ok = !error;

    if (error) {
        if (m_http) {
            m_errorString = m_http->errorString();
        } else if (m_ftp) {
            m_errorString = m_ftp->errorString();
        }
    }

    if (m_lastStatus / 100 >= 4) {
        m_ok = false;
    }

    delete m_localFile;
    m_localFile = 0;

    delete m_progressDialog;
    m_progressDialog = 0;

    if (m_ok) {
        QFileInfo fi(m_localFilename);
        if (!fi.exists()) {
            m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
            m_ok = false;
        } else if (fi.size() == 0) {
            m_errorString = tr("File contains no data!");
            m_ok = false;
        }
    }
    m_done = true;
}

void
RemoteFile::showProgressDialog()
{
    if (m_progressDialog) m_progressDialog->show();
}

QString
RemoteFile::createLocalFile(QUrl url)
{
    //!!! should we actually put up dialogs for these errors? or propagate an exception?
    
    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;
}