annotate data/fileio/RemoteFile.cpp @ 308:14e0f60435b8

* Make it possible to drop audio files, layer files, session files and images onto SV panes. Need to do a bit more work on where we expect the dropped file to go, particularly in the case of audio files -- at the moment they're always opened in new panes, but it may be better to by default replace whatever is in the target pane.
author Chris Cannam
date Wed, 10 Oct 2007 15:18:02 +0000
parents 4fc6f49436b3
children 96ef9746c560
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@304 36 RemoteFile::RemoteRefCountMap
Chris@304 37 RemoteFile::m_refCountMap;
Chris@304 38
Chris@304 39 RemoteFile::RemoteLocalMap
Chris@304 40 RemoteFile::m_remoteLocalMap;
Chris@304 41
Chris@304 42 QMutex
Chris@304 43 RemoteFile::m_mapMutex;
Chris@304 44
Chris@208 45 RemoteFile::RemoteFile(QUrl url) :
Chris@304 46 m_url(url),
Chris@208 47 m_ftp(0),
Chris@208 48 m_http(0),
Chris@208 49 m_localFile(0),
Chris@208 50 m_ok(false),
Chris@210 51 m_lastStatus(0),
Chris@208 52 m_done(false),
Chris@210 53 m_progressDialog(0),
Chris@304 54 m_progressShowTimer(this),
Chris@304 55 m_referenced(false)
Chris@208 56 {
Chris@208 57 if (!canHandleScheme(url)) {
Chris@208 58 std::cerr << "RemoteFile::RemoteFile: ERROR: Unsupported scheme in URL \"" << url.toString().toStdString() << "\"" << std::endl;
Chris@208 59 return;
Chris@208 60 }
Chris@208 61
Chris@304 62 QMutexLocker locker(&m_mapMutex);
Chris@304 63
Chris@304 64 std::cerr << "RemoteFile::RemoteFile: refcount is " << m_refCountMap[m_url] << std::endl;
Chris@304 65
Chris@304 66 if (m_refCountMap[m_url] > 0) {
Chris@304 67 m_refCountMap[m_url]++;
Chris@304 68 m_localFilename = m_remoteLocalMap[m_url];
Chris@304 69 std::cerr << "raising it" << std::endl;
Chris@304 70 m_ok = true;
Chris@304 71 m_done = true;
Chris@304 72 m_referenced = true;
Chris@304 73 return;
Chris@304 74 }
Chris@304 75
Chris@208 76 m_localFilename = createLocalFile(url);
Chris@208 77 if (m_localFilename == "") return;
Chris@208 78 m_localFile = new QFile(m_localFilename);
Chris@208 79 m_localFile->open(QFile::WriteOnly);
Chris@208 80
Chris@208 81 QString scheme = url.scheme().toLower();
Chris@208 82
Chris@208 83 if (scheme == "http") {
Chris@208 84
Chris@211 85 m_ok = true;
Chris@208 86 m_http = new QHttp(url.host(), url.port(80));
Chris@208 87 connect(m_http, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@208 88 connect(m_http, SIGNAL(dataReadProgress(int, int)),
Chris@208 89 this, SLOT(dataReadProgress(int, int)));
Chris@210 90 connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)),
Chris@214 91 this, SLOT(httpResponseHeaderReceived(const QHttpResponseHeader &)));
Chris@279 92
Chris@279 93 // I don't quite understand this. url.path() returns a path
Chris@279 94 // without percent encoding; for example, spaces appear as
Chris@279 95 // literal spaces. This generally won't work if sent to the
Chris@279 96 // server directly. You can retrieve a correctly encoded URL
Chris@279 97 // from QUrl using url.toEncoded(), but that gives you the
Chris@279 98 // whole URL; there doesn't seem to be any way to retrieve
Chris@279 99 // only an encoded path. Furthermore there doesn't seem to be
Chris@279 100 // any way to convert a retrieved path into an encoded path
Chris@279 101 // without explicitly specifying that you don't want the path
Chris@279 102 // separators ("/") to be encoded. (Besides being painful to
Chris@279 103 // manage, I don't see how this can work correctly in any case
Chris@279 104 // where a percent-encoded "/" is supposed to appear within a
Chris@279 105 // path element?) There also seems to be no way to retrieve
Chris@279 106 // the path plus query string, i.e. everything that I need to
Chris@279 107 // send to the HTTP server. And no way for QHttp to take a
Chris@279 108 // QUrl argument. I'm obviously missing something.
Chris@279 109
Chris@279 110 // So, two ways to do this: query the bits from the URL,
Chris@279 111 // encode them individually, and glue them back together
Chris@279 112 // again...
Chris@279 113 /*
Chris@279 114 QString path = QUrl::toPercentEncoding(url.path(), "/");
Chris@279 115 QList<QPair<QString, QString> > query = url.queryItems();
Chris@279 116 if (!query.empty()) {
Chris@279 117 QStringList q2;
Chris@279 118 for (QList<QPair<QString, QString> >::iterator i = query.begin();
Chris@279 119 i != query.end(); ++i) {
Chris@279 120 q2.push_back(QString("%1=%3")
Chris@279 121 .arg(QString(QUrl::toPercentEncoding(i->first)))
Chris@279 122 .arg(QString(QUrl::toPercentEncoding(i->second))));
Chris@279 123 }
Chris@279 124 path = QString("%1%2%3")
Chris@279 125 .arg(path).arg("?")
Chris@279 126 .arg(q2.join("&"));
Chris@279 127 }
Chris@279 128 */
Chris@279 129
Chris@279 130 // ...or, much simpler but relying on knowledge about the
Chris@279 131 // scheme://host/path/path/query etc format of the URL, we can
Chris@279 132 // get the whole URL ready-encoded and then split it on "/" as
Chris@279 133 // appropriate...
Chris@279 134
Chris@279 135 QString path = "/" + QString(url.toEncoded()).section('/', 3);
Chris@279 136
Chris@279 137 std::cerr << "RemoteFile: path is \""
Chris@279 138 << path.toStdString() << "\"" << std::endl;
Chris@279 139
Chris@278 140 m_http->get(path, m_localFile);
Chris@208 141
Chris@208 142 } else if (scheme == "ftp") {
Chris@208 143
Chris@211 144 m_ok = true;
Chris@208 145 m_ftp = new QFtp;
Chris@208 146 connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@214 147 connect(m_ftp, SIGNAL(commandFinished(int, bool)),
Chris@214 148 this, SLOT(ftpCommandFinished(int, bool)));
Chris@208 149 connect(m_ftp, SIGNAL(dataTransferProgress(qint64, qint64)),
Chris@208 150 this, SLOT(dataTransferProgress(qint64, qint64)));
Chris@208 151 m_ftp->connectToHost(url.host(), url.port(21));
Chris@208 152
Chris@208 153 QString username = url.userName();
Chris@208 154 if (username == "") {
Chris@208 155 username = "anonymous";
Chris@208 156 }
Chris@208 157
Chris@208 158 QString password = url.password();
Chris@208 159 if (password == "") {
Chris@208 160 password = QString("%1@%2").arg(getenv("USER")).arg(getenv("HOST"));
Chris@208 161 }
Chris@208 162
Chris@214 163 m_ftp->login(username, password);
Chris@214 164
Chris@214 165 QString dirpath = url.path().section('/', 0, -2);
Chris@214 166 QString filename = url.path().section('/', -1);
Chris@214 167
Chris@214 168 if (dirpath == "") dirpath = "/";
Chris@214 169 m_ftp->cd(dirpath);
Chris@214 170 m_ftp->get(filename, m_localFile);
Chris@208 171 }
Chris@208 172
Chris@208 173 if (m_ok) {
Chris@304 174
Chris@304 175 m_remoteLocalMap[m_url] = m_localFilename;
Chris@304 176 m_refCountMap[m_url]++;
Chris@304 177 m_referenced = true;
Chris@304 178
Chris@208 179 m_progressDialog = new QProgressDialog(tr("Downloading %1...").arg(url.toString()), tr("Cancel"), 0, 100);
Chris@210 180 m_progressDialog->hide();
Chris@210 181 connect(&m_progressShowTimer, SIGNAL(timeout()),
Chris@210 182 this, SLOT(showProgressDialog()));
Chris@210 183 connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(cancelled()));
Chris@210 184 m_progressShowTimer.setSingleShot(true);
Chris@210 185 m_progressShowTimer.start(2000);
Chris@208 186 }
Chris@208 187 }
Chris@208 188
Chris@208 189 RemoteFile::~RemoteFile()
Chris@208 190 {
Chris@211 191 cleanup();
Chris@211 192 }
Chris@211 193
Chris@211 194 void
Chris@211 195 RemoteFile::cleanup()
Chris@211 196 {
Chris@211 197 m_done = true;
Chris@214 198 if (m_http) {
Chris@287 199 QHttp *h = m_http;
Chris@214 200 m_http = 0;
Chris@287 201 h->abort();
Chris@287 202 h->deleteLater();
Chris@214 203 }
Chris@214 204 if (m_ftp) {
Chris@287 205 QFtp *f = m_ftp;
Chris@214 206 m_ftp = 0;
Chris@287 207 f->abort();
Chris@287 208 f->deleteLater();
Chris@214 209 }
Chris@211 210 delete m_progressDialog;
Chris@211 211 m_progressDialog = 0;
Chris@304 212 delete m_localFile; // does not actually delete the file
Chris@211 213 m_localFile = 0;
Chris@208 214 }
Chris@208 215
Chris@208 216 bool
Chris@304 217 RemoteFile::isRemote(QString fileOrUrl)
Chris@304 218 {
Chris@304 219 return (fileOrUrl.startsWith("http:") || fileOrUrl.startsWith("ftp:"));
Chris@304 220 }
Chris@304 221
Chris@304 222 bool
Chris@208 223 RemoteFile::canHandleScheme(QUrl url)
Chris@208 224 {
Chris@208 225 QString scheme = url.scheme().toLower();
Chris@208 226 return (scheme == "http" || scheme == "ftp");
Chris@208 227 }
Chris@208 228
Chris@210 229 bool
Chris@210 230 RemoteFile::isAvailable()
Chris@210 231 {
Chris@211 232 while (m_ok && (!m_done && m_lastStatus == 0)) {
Chris@210 233 QApplication::processEvents();
Chris@210 234 }
Chris@211 235 bool available = true;
Chris@211 236 if (!m_ok) available = false;
Chris@211 237 else available = (m_lastStatus / 100 == 2);
Chris@211 238 std::cerr << "RemoteFile::isAvailable: " << (available ? "yes" : "no")
Chris@211 239 << std::endl;
Chris@211 240 return available;
Chris@210 241 }
Chris@210 242
Chris@208 243 void
Chris@208 244 RemoteFile::wait()
Chris@208 245 {
Chris@211 246 while (m_ok && !m_done) {
Chris@208 247 QApplication::processEvents();
Chris@208 248 }
Chris@208 249 }
Chris@208 250
Chris@208 251 bool
Chris@208 252 RemoteFile::isOK() const
Chris@208 253 {
Chris@208 254 return m_ok;
Chris@208 255 }
Chris@208 256
Chris@208 257 bool
Chris@208 258 RemoteFile::isDone() const
Chris@208 259 {
Chris@208 260 return m_done;
Chris@208 261 }
Chris@208 262
Chris@208 263 QString
Chris@208 264 RemoteFile::getLocalFilename() const
Chris@208 265 {
Chris@208 266 return m_localFilename;
Chris@208 267 }
Chris@208 268
Chris@208 269 QString
Chris@208 270 RemoteFile::getErrorString() const
Chris@208 271 {
Chris@208 272 return m_errorString;
Chris@208 273 }
Chris@208 274
Chris@208 275 void
Chris@208 276 RemoteFile::dataReadProgress(int done, int total)
Chris@208 277 {
Chris@208 278 dataTransferProgress(done, total);
Chris@208 279 }
Chris@208 280
Chris@208 281 void
Chris@214 282 RemoteFile::httpResponseHeaderReceived(const QHttpResponseHeader &resp)
Chris@210 283 {
Chris@210 284 m_lastStatus = resp.statusCode();
Chris@210 285 if (m_lastStatus / 100 >= 4) {
Chris@210 286 m_errorString = QString("%1 %2")
Chris@210 287 .arg(resp.statusCode()).arg(resp.reasonPhrase());
Chris@211 288 std::cerr << "RemoteFile::responseHeaderReceived: "
Chris@211 289 << m_errorString.toStdString() << std::endl;
Chris@211 290 } else {
Chris@211 291 std::cerr << "RemoteFile::responseHeaderReceived: "
Chris@211 292 << m_lastStatus << std::endl;
Chris@211 293 }
Chris@210 294 }
Chris@210 295
Chris@210 296 void
Chris@214 297 RemoteFile::ftpCommandFinished(int id, bool error)
Chris@214 298 {
Chris@214 299 std::cerr << "RemoteFile::ftpCommandFinished(" << id << ", " << error << ")" << std::endl;
Chris@214 300
Chris@214 301 if (!m_ftp) return;
Chris@214 302
Chris@214 303 QFtp::Command command = m_ftp->currentCommand();
Chris@214 304
Chris@214 305 if (!error) {
Chris@214 306 std::cerr << "RemoteFile::ftpCommandFinished: success for command "
Chris@214 307 << command << std::endl;
Chris@214 308 return;
Chris@214 309 }
Chris@214 310
Chris@214 311 if (command == QFtp::ConnectToHost) {
Chris@214 312 m_errorString = tr("Failed to connect to FTP server");
Chris@214 313 } else if (command == QFtp::Login) {
Chris@214 314 m_errorString = tr("Login failed");
Chris@214 315 } else if (command == QFtp::Cd) {
Chris@214 316 m_errorString = tr("Failed to change to correct directory");
Chris@214 317 } else if (command == QFtp::Get) {
Chris@214 318 m_errorString = tr("FTP download aborted");
Chris@214 319 }
Chris@214 320
Chris@214 321 m_lastStatus = 400; // for done()
Chris@214 322 }
Chris@214 323
Chris@214 324 void
Chris@208 325 RemoteFile::dataTransferProgress(qint64 done, qint64 total)
Chris@208 326 {
Chris@211 327 if (!m_progressDialog) return;
Chris@211 328
Chris@208 329 int percent = int((double(done) / double(total)) * 100.0 - 0.1);
Chris@208 330 emit progress(percent);
Chris@208 331
Chris@211 332 if (percent > 0) {
Chris@211 333 m_progressDialog->setValue(percent);
Chris@211 334 m_progressDialog->show();
Chris@211 335 }
Chris@210 336 }
Chris@210 337
Chris@210 338 void
Chris@210 339 RemoteFile::cancelled()
Chris@210 340 {
Chris@211 341 deleteLocalFile();
Chris@210 342 m_done = true;
Chris@210 343 m_ok = false;
Chris@210 344 m_errorString = tr("Download cancelled");
Chris@208 345 }
Chris@208 346
Chris@208 347 void
Chris@208 348 RemoteFile::done(bool error)
Chris@208 349 {
Chris@214 350 std::cerr << "RemoteFile::done(" << error << ")" << std::endl;
Chris@211 351
Chris@211 352 if (m_done) return;
Chris@211 353
Chris@208 354 emit progress(100);
Chris@210 355
Chris@208 356 if (error) {
Chris@208 357 if (m_http) {
Chris@208 358 m_errorString = m_http->errorString();
Chris@208 359 } else if (m_ftp) {
Chris@208 360 m_errorString = m_ftp->errorString();
Chris@208 361 }
Chris@208 362 }
Chris@208 363
Chris@210 364 if (m_lastStatus / 100 >= 4) {
Chris@211 365 error = true;
Chris@210 366 }
Chris@210 367
Chris@211 368 cleanup();
Chris@208 369
Chris@211 370 if (!error) {
Chris@208 371 QFileInfo fi(m_localFilename);
Chris@208 372 if (!fi.exists()) {
Chris@208 373 m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
Chris@211 374 error = true;
Chris@208 375 } else if (fi.size() == 0) {
Chris@208 376 m_errorString = tr("File contains no data!");
Chris@211 377 error = true;
Chris@208 378 }
Chris@208 379 }
Chris@211 380
Chris@211 381 if (error) {
Chris@211 382 deleteLocalFile();
Chris@211 383 }
Chris@211 384
Chris@211 385 m_ok = !error;
Chris@211 386 m_done = true;
Chris@304 387 emit ready();
Chris@211 388 }
Chris@211 389
Chris@211 390 void
Chris@211 391 RemoteFile::deleteLocalFile()
Chris@211 392 {
Chris@211 393 // std::cerr << "RemoteFile::deleteLocalFile" << std::endl;
Chris@211 394
Chris@211 395 cleanup();
Chris@211 396
Chris@211 397 if (m_localFilename == "") return;
Chris@211 398
Chris@304 399 if (m_referenced) {
Chris@304 400
Chris@304 401 QMutexLocker locker(&m_mapMutex);
Chris@304 402 m_referenced = false;
Chris@304 403
Chris@304 404 if (m_refCountMap[m_url] > 0) {
Chris@304 405 m_refCountMap[m_url]--;
Chris@304 406 if (m_refCountMap[m_url] > 0) {
Chris@304 407 m_done = true;
Chris@304 408 return;
Chris@304 409 }
Chris@304 410 }
Chris@304 411 }
Chris@304 412
Chris@211 413 m_fileCreationMutex.lock();
Chris@211 414
Chris@211 415 if (!QFile(m_localFilename).remove()) {
Chris@211 416 std::cerr << "RemoteFile::deleteLocalFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@211 417 } else {
Chris@211 418 m_localFilename = "";
Chris@211 419 }
Chris@211 420
Chris@211 421 m_fileCreationMutex.unlock();
Chris@211 422
Chris@208 423 m_done = true;
Chris@208 424 }
Chris@208 425
Chris@210 426 void
Chris@210 427 RemoteFile::showProgressDialog()
Chris@210 428 {
Chris@210 429 if (m_progressDialog) m_progressDialog->show();
Chris@210 430 }
Chris@210 431
Chris@208 432 QString
Chris@208 433 RemoteFile::createLocalFile(QUrl url)
Chris@208 434 {
Chris@208 435 QDir dir;
Chris@208 436 try {
Chris@208 437 dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
Chris@208 438 } catch (DirectoryCreationFailed f) {
Chris@208 439 std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl;
Chris@208 440 return "";
Chris@208 441 }
Chris@208 442
Chris@208 443 QString filepart = url.path().section('/', -1, -1,
Chris@208 444 QString::SectionSkipEmpty);
Chris@208 445
Chris@208 446 QString extension = filepart.section('.', -1);
Chris@208 447 QString base = filepart;
Chris@208 448 if (extension != "") {
Chris@208 449 base = base.left(base.length() - extension.length() - 1);
Chris@208 450 }
Chris@208 451 if (base == "") base = "remote";
Chris@208 452
Chris@208 453 QString filename;
Chris@208 454
Chris@208 455 if (extension == "") {
Chris@208 456 filename = base;
Chris@208 457 } else {
Chris@208 458 filename = QString("%1.%2").arg(base).arg(extension);
Chris@208 459 }
Chris@208 460
Chris@208 461 QString filepath(dir.filePath(filename));
Chris@208 462
Chris@208 463 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 464
Chris@208 465 m_fileCreationMutex.lock();
Chris@208 466 ++m_count;
Chris@208 467
Chris@208 468 if (QFileInfo(filepath).exists() ||
Chris@208 469 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 470
Chris@208 471 std::cerr << "RemoteFile::createLocalFile: Failed to create local file \""
Chris@208 472 << filepath.toStdString() << "\" for URL \""
Chris@208 473 << url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl;
Chris@208 474
Chris@208 475
Chris@208 476 if (extension == "") {
Chris@208 477 filename = QString("%1_%2").arg(base).arg(m_count);
Chris@208 478 } else {
Chris@208 479 filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
Chris@208 480 }
Chris@208 481 filepath = dir.filePath(filename);
Chris@208 482
Chris@208 483 if (QFileInfo(filepath).exists() ||
Chris@208 484 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 485
Chris@208 486 std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create local file \""
Chris@208 487 << filepath.toStdString() << "\" for URL \""
Chris@208 488 << url.toString().toStdString() << "\" (or file already exists)" << std::endl;
Chris@208 489
Chris@208 490 m_fileCreationMutex.unlock();
Chris@208 491 return "";
Chris@208 492 }
Chris@208 493 }
Chris@208 494
Chris@208 495 m_fileCreationMutex.unlock();
Chris@208 496
Chris@208 497 std::cerr << "RemoteFile::createLocalFile: url "
Chris@208 498 << url.toString().toStdString() << " -> local filename "
Chris@208 499 << filepath.toStdString() << std::endl;
Chris@208 500
Chris@208 501 return filepath;
Chris@208 502 }