annotate data/fileio/RemoteFile.cpp @ 211:e2bbb58e6df6

Several changes related to referring to remote URLs for sessions and files: * Pull file dialog wrapper functions out from MainWindow into FileFinder * If a file referred to in a session is not found at its expected location, try a few other alternatives (same location as the session file or same location as the last audio file) before asking the user to locate it * Allow user to give a URL when locating an audio file, not just locate on the filesystem * Make wave file models remember the "original" location (e.g. URL) of the audio file, not just the actual location from which the data was loaded (e.g. local copy of that URL) -- when saving a session, use the original location so as not to refer to a temporary file * Clean up incompletely-downloaded local copies of files
author Chris Cannam
date Thu, 11 Jan 2007 13:29:58 +0000
parents a06afefe45ee
children ce6f65ab3327
rev   line source
Chris@208 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@208 2
Chris@208 3 /*
Chris@208 4 Sonic Visualiser
Chris@208 5 An audio file viewer and annotation editor.
Chris@208 6 Centre for Digital Music, Queen Mary, University of London.
Chris@208 7 This file copyright 2007 QMUL.
Chris@208 8
Chris@208 9 This program is free software; you can redistribute it and/or
Chris@208 10 modify it under the terms of the GNU General Public License as
Chris@208 11 published by the Free Software Foundation; either version 2 of the
Chris@208 12 License, or (at your option) any later version. See the file
Chris@208 13 COPYING included with this distribution for more information.
Chris@208 14 */
Chris@208 15
Chris@208 16 #include "RemoteFile.h"
Chris@208 17 #include "base/TempDirectory.h"
Chris@208 18 #include "base/Exceptions.h"
Chris@208 19
Chris@208 20 #include <QHttp>
Chris@208 21 #include <QFtp>
Chris@208 22 #include <QFileInfo>
Chris@208 23 #include <QDir>
Chris@208 24 #include <QApplication>
Chris@208 25 #include <QProgressDialog>
Chris@210 26 #include <QHttpResponseHeader>
Chris@208 27
Chris@208 28 #include <iostream>
Chris@208 29
Chris@208 30 int
Chris@208 31 RemoteFile::m_count = 0;
Chris@208 32
Chris@208 33 QMutex
Chris@208 34 RemoteFile::m_fileCreationMutex;
Chris@208 35
Chris@208 36 RemoteFile::RemoteFile(QUrl url) :
Chris@208 37 m_ftp(0),
Chris@208 38 m_http(0),
Chris@208 39 m_localFile(0),
Chris@208 40 m_ok(false),
Chris@210 41 m_lastStatus(0),
Chris@208 42 m_done(false),
Chris@210 43 m_progressDialog(0),
Chris@210 44 m_progressShowTimer(this)
Chris@208 45 {
Chris@208 46 if (!canHandleScheme(url)) {
Chris@208 47 std::cerr << "RemoteFile::RemoteFile: ERROR: Unsupported scheme in URL \"" << url.toString().toStdString() << "\"" << std::endl;
Chris@208 48 return;
Chris@208 49 }
Chris@208 50
Chris@208 51 m_localFilename = createLocalFile(url);
Chris@208 52 if (m_localFilename == "") return;
Chris@208 53 m_localFile = new QFile(m_localFilename);
Chris@208 54 m_localFile->open(QFile::WriteOnly);
Chris@208 55
Chris@208 56 QString scheme = url.scheme().toLower();
Chris@208 57
Chris@208 58 if (scheme == "http") {
Chris@208 59
Chris@211 60 m_ok = true;
Chris@208 61 m_http = new QHttp(url.host(), url.port(80));
Chris@208 62 connect(m_http, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@208 63 connect(m_http, SIGNAL(dataReadProgress(int, int)),
Chris@208 64 this, SLOT(dataReadProgress(int, int)));
Chris@210 65 connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)),
Chris@210 66 this, SLOT(responseHeaderReceived(const QHttpResponseHeader &)));
Chris@208 67 m_http->get(url.path(), m_localFile);
Chris@208 68
Chris@208 69 } else if (scheme == "ftp") {
Chris@208 70
Chris@211 71 m_ok = true;
Chris@208 72 m_ftp = new QFtp;
Chris@208 73 connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@208 74 connect(m_ftp, SIGNAL(dataTransferProgress(qint64, qint64)),
Chris@208 75 this, SLOT(dataTransferProgress(qint64, qint64)));
Chris@208 76 m_ftp->connectToHost(url.host(), url.port(21));
Chris@208 77
Chris@208 78 QString username = url.userName();
Chris@208 79 if (username == "") {
Chris@208 80 username = "anonymous";
Chris@208 81 }
Chris@208 82
Chris@208 83 QString password = url.password();
Chris@208 84 if (password == "") {
Chris@208 85 password = QString("%1@%2").arg(getenv("USER")).arg(getenv("HOST"));
Chris@208 86 }
Chris@208 87
Chris@208 88 QStringList path = url.path().split('/');
Chris@208 89 for (QStringList::iterator i = path.begin(); i != path.end(); ) {
Chris@208 90 QString bit = *i;
Chris@208 91 ++i;
Chris@208 92 if (i != path.end()) {
Chris@208 93 m_ftp->cd(*i);
Chris@208 94 } else {
Chris@208 95 m_ftp->get(*i, m_localFile);
Chris@208 96 }
Chris@208 97 }
Chris@208 98 }
Chris@208 99
Chris@208 100 if (m_ok) {
Chris@208 101 m_progressDialog = new QProgressDialog(tr("Downloading %1...").arg(url.toString()), tr("Cancel"), 0, 100);
Chris@210 102 m_progressDialog->hide();
Chris@210 103 connect(&m_progressShowTimer, SIGNAL(timeout()),
Chris@210 104 this, SLOT(showProgressDialog()));
Chris@210 105 connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(cancelled()));
Chris@210 106 m_progressShowTimer.setSingleShot(true);
Chris@210 107 m_progressShowTimer.start(2000);
Chris@208 108 }
Chris@208 109 }
Chris@208 110
Chris@208 111 RemoteFile::~RemoteFile()
Chris@208 112 {
Chris@211 113 cleanup();
Chris@211 114 }
Chris@211 115
Chris@211 116 void
Chris@211 117 RemoteFile::cleanup()
Chris@211 118 {
Chris@211 119 // std::cerr << "RemoteFile::cleanup" << std::endl;
Chris@211 120 m_done = true;
Chris@211 121 delete m_http;
Chris@211 122 m_http = 0;
Chris@208 123 delete m_ftp;
Chris@211 124 m_ftp = 0;
Chris@211 125 delete m_progressDialog;
Chris@211 126 m_progressDialog = 0;
Chris@208 127 delete m_localFile;
Chris@211 128 m_localFile = 0;
Chris@208 129 }
Chris@208 130
Chris@208 131 bool
Chris@208 132 RemoteFile::canHandleScheme(QUrl url)
Chris@208 133 {
Chris@208 134 QString scheme = url.scheme().toLower();
Chris@208 135 return (scheme == "http" || scheme == "ftp");
Chris@208 136 }
Chris@208 137
Chris@210 138 bool
Chris@210 139 RemoteFile::isAvailable()
Chris@210 140 {
Chris@211 141 while (m_ok && (!m_done && m_lastStatus == 0)) {
Chris@210 142 QApplication::processEvents();
Chris@210 143 }
Chris@211 144 bool available = true;
Chris@211 145 if (!m_ok) available = false;
Chris@211 146 else available = (m_lastStatus / 100 == 2);
Chris@211 147 std::cerr << "RemoteFile::isAvailable: " << (available ? "yes" : "no")
Chris@211 148 << std::endl;
Chris@211 149 return available;
Chris@210 150 }
Chris@210 151
Chris@208 152 void
Chris@208 153 RemoteFile::wait()
Chris@208 154 {
Chris@211 155 while (m_ok && !m_done) {
Chris@208 156 QApplication::processEvents();
Chris@208 157 }
Chris@208 158 }
Chris@208 159
Chris@208 160 bool
Chris@208 161 RemoteFile::isOK() const
Chris@208 162 {
Chris@208 163 return m_ok;
Chris@208 164 }
Chris@208 165
Chris@208 166 bool
Chris@208 167 RemoteFile::isDone() const
Chris@208 168 {
Chris@208 169 return m_done;
Chris@208 170 }
Chris@208 171
Chris@208 172 QString
Chris@208 173 RemoteFile::getLocalFilename() const
Chris@208 174 {
Chris@208 175 return m_localFilename;
Chris@208 176 }
Chris@208 177
Chris@208 178 QString
Chris@208 179 RemoteFile::getErrorString() const
Chris@208 180 {
Chris@208 181 return m_errorString;
Chris@208 182 }
Chris@208 183
Chris@208 184 void
Chris@208 185 RemoteFile::dataReadProgress(int done, int total)
Chris@208 186 {
Chris@208 187 dataTransferProgress(done, total);
Chris@208 188 }
Chris@208 189
Chris@208 190 void
Chris@210 191 RemoteFile::responseHeaderReceived(const QHttpResponseHeader &resp)
Chris@210 192 {
Chris@210 193 m_lastStatus = resp.statusCode();
Chris@210 194 if (m_lastStatus / 100 >= 4) {
Chris@210 195 m_errorString = QString("%1 %2")
Chris@210 196 .arg(resp.statusCode()).arg(resp.reasonPhrase());
Chris@211 197 std::cerr << "RemoteFile::responseHeaderReceived: "
Chris@211 198 << m_errorString.toStdString() << std::endl;
Chris@211 199 } else {
Chris@211 200 std::cerr << "RemoteFile::responseHeaderReceived: "
Chris@211 201 << m_lastStatus << std::endl;
Chris@211 202 }
Chris@210 203 }
Chris@210 204
Chris@210 205 void
Chris@208 206 RemoteFile::dataTransferProgress(qint64 done, qint64 total)
Chris@208 207 {
Chris@211 208 if (!m_progressDialog) return;
Chris@211 209
Chris@208 210 int percent = int((double(done) / double(total)) * 100.0 - 0.1);
Chris@208 211 emit progress(percent);
Chris@208 212
Chris@211 213 if (percent > 0) {
Chris@211 214 m_progressDialog->setValue(percent);
Chris@211 215 m_progressDialog->show();
Chris@211 216 }
Chris@210 217 }
Chris@210 218
Chris@210 219 void
Chris@210 220 RemoteFile::cancelled()
Chris@210 221 {
Chris@211 222 deleteLocalFile();
Chris@210 223 m_done = true;
Chris@210 224 m_ok = false;
Chris@210 225 m_errorString = tr("Download cancelled");
Chris@208 226 }
Chris@208 227
Chris@208 228 void
Chris@208 229 RemoteFile::done(bool error)
Chris@208 230 {
Chris@211 231 // std::cerr << "RemoteFile::done(" << error << ")" << std::endl;
Chris@211 232
Chris@211 233 if (m_done) return;
Chris@211 234
Chris@208 235 emit progress(100);
Chris@210 236
Chris@208 237 if (error) {
Chris@208 238 if (m_http) {
Chris@208 239 m_errorString = m_http->errorString();
Chris@208 240 } else if (m_ftp) {
Chris@208 241 m_errorString = m_ftp->errorString();
Chris@208 242 }
Chris@208 243 }
Chris@208 244
Chris@210 245 if (m_lastStatus / 100 >= 4) {
Chris@211 246 error = true;
Chris@210 247 }
Chris@210 248
Chris@211 249 cleanup();
Chris@208 250
Chris@211 251 if (!error) {
Chris@208 252 QFileInfo fi(m_localFilename);
Chris@208 253 if (!fi.exists()) {
Chris@208 254 m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
Chris@211 255 error = true;
Chris@208 256 } else if (fi.size() == 0) {
Chris@208 257 m_errorString = tr("File contains no data!");
Chris@211 258 error = true;
Chris@208 259 }
Chris@208 260 }
Chris@211 261
Chris@211 262 if (error) {
Chris@211 263 deleteLocalFile();
Chris@211 264 }
Chris@211 265
Chris@211 266 m_ok = !error;
Chris@211 267 m_done = true;
Chris@211 268 }
Chris@211 269
Chris@211 270 void
Chris@211 271 RemoteFile::deleteLocalFile()
Chris@211 272 {
Chris@211 273 // std::cerr << "RemoteFile::deleteLocalFile" << std::endl;
Chris@211 274
Chris@211 275 cleanup();
Chris@211 276
Chris@211 277 if (m_localFilename == "") return;
Chris@211 278
Chris@211 279 m_fileCreationMutex.lock();
Chris@211 280
Chris@211 281 if (!QFile(m_localFilename).remove()) {
Chris@211 282 std::cerr << "RemoteFile::deleteLocalFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@211 283 } else {
Chris@211 284 m_localFilename = "";
Chris@211 285 }
Chris@211 286
Chris@211 287 m_fileCreationMutex.unlock();
Chris@211 288
Chris@208 289 m_done = true;
Chris@208 290 }
Chris@208 291
Chris@210 292 void
Chris@210 293 RemoteFile::showProgressDialog()
Chris@210 294 {
Chris@210 295 if (m_progressDialog) m_progressDialog->show();
Chris@210 296 }
Chris@210 297
Chris@208 298 QString
Chris@208 299 RemoteFile::createLocalFile(QUrl url)
Chris@208 300 {
Chris@208 301 QDir dir;
Chris@208 302 try {
Chris@208 303 dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
Chris@208 304 } catch (DirectoryCreationFailed f) {
Chris@208 305 std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl;
Chris@208 306 return "";
Chris@208 307 }
Chris@208 308
Chris@208 309 QString filepart = url.path().section('/', -1, -1,
Chris@208 310 QString::SectionSkipEmpty);
Chris@208 311
Chris@208 312 QString extension = filepart.section('.', -1);
Chris@208 313 QString base = filepart;
Chris@208 314 if (extension != "") {
Chris@208 315 base = base.left(base.length() - extension.length() - 1);
Chris@208 316 }
Chris@208 317 if (base == "") base = "remote";
Chris@208 318
Chris@208 319 QString filename;
Chris@208 320
Chris@208 321 if (extension == "") {
Chris@208 322 filename = base;
Chris@208 323 } else {
Chris@208 324 filename = QString("%1.%2").arg(base).arg(extension);
Chris@208 325 }
Chris@208 326
Chris@208 327 QString filepath(dir.filePath(filename));
Chris@208 328
Chris@208 329 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 330
Chris@208 331 m_fileCreationMutex.lock();
Chris@208 332 ++m_count;
Chris@208 333
Chris@208 334 if (QFileInfo(filepath).exists() ||
Chris@208 335 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 336
Chris@208 337 std::cerr << "RemoteFile::createLocalFile: Failed to create local file \""
Chris@208 338 << filepath.toStdString() << "\" for URL \""
Chris@208 339 << url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl;
Chris@208 340
Chris@208 341
Chris@208 342 if (extension == "") {
Chris@208 343 filename = QString("%1_%2").arg(base).arg(m_count);
Chris@208 344 } else {
Chris@208 345 filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
Chris@208 346 }
Chris@208 347 filepath = dir.filePath(filename);
Chris@208 348
Chris@208 349 if (QFileInfo(filepath).exists() ||
Chris@208 350 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 351
Chris@208 352 std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create local file \""
Chris@208 353 << filepath.toStdString() << "\" for URL \""
Chris@208 354 << url.toString().toStdString() << "\" (or file already exists)" << std::endl;
Chris@208 355
Chris@208 356 m_fileCreationMutex.unlock();
Chris@208 357 return "";
Chris@208 358 }
Chris@208 359 }
Chris@208 360
Chris@208 361 m_fileCreationMutex.unlock();
Chris@208 362
Chris@208 363 std::cerr << "RemoteFile::createLocalFile: url "
Chris@208 364 << url.toString().toStdString() << " -> local filename "
Chris@208 365 << filepath.toStdString() << std::endl;
Chris@208 366
Chris@208 367 return filepath;
Chris@208 368 }