annotate data/fileio/RemoteFile.cpp @ 315:96ef9746c560

* Add content type to remote file
author Chris Cannam
date Thu, 18 Oct 2007 10:24:26 +0000
parents 4fc6f49436b3
children 3a6725f285d6
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@315 293 if (resp.hasContentType()) m_contentType = resp.contentType();
Chris@211 294 }
Chris@210 295 }
Chris@210 296
Chris@210 297 void
Chris@214 298 RemoteFile::ftpCommandFinished(int id, bool error)
Chris@214 299 {
Chris@214 300 std::cerr << "RemoteFile::ftpCommandFinished(" << id << ", " << error << ")" << std::endl;
Chris@214 301
Chris@214 302 if (!m_ftp) return;
Chris@214 303
Chris@214 304 QFtp::Command command = m_ftp->currentCommand();
Chris@214 305
Chris@214 306 if (!error) {
Chris@214 307 std::cerr << "RemoteFile::ftpCommandFinished: success for command "
Chris@214 308 << command << std::endl;
Chris@214 309 return;
Chris@214 310 }
Chris@214 311
Chris@214 312 if (command == QFtp::ConnectToHost) {
Chris@214 313 m_errorString = tr("Failed to connect to FTP server");
Chris@214 314 } else if (command == QFtp::Login) {
Chris@214 315 m_errorString = tr("Login failed");
Chris@214 316 } else if (command == QFtp::Cd) {
Chris@214 317 m_errorString = tr("Failed to change to correct directory");
Chris@214 318 } else if (command == QFtp::Get) {
Chris@214 319 m_errorString = tr("FTP download aborted");
Chris@214 320 }
Chris@214 321
Chris@214 322 m_lastStatus = 400; // for done()
Chris@214 323 }
Chris@214 324
Chris@214 325 void
Chris@208 326 RemoteFile::dataTransferProgress(qint64 done, qint64 total)
Chris@208 327 {
Chris@211 328 if (!m_progressDialog) return;
Chris@211 329
Chris@208 330 int percent = int((double(done) / double(total)) * 100.0 - 0.1);
Chris@208 331 emit progress(percent);
Chris@208 332
Chris@211 333 if (percent > 0) {
Chris@211 334 m_progressDialog->setValue(percent);
Chris@211 335 m_progressDialog->show();
Chris@211 336 }
Chris@210 337 }
Chris@210 338
Chris@210 339 void
Chris@210 340 RemoteFile::cancelled()
Chris@210 341 {
Chris@211 342 deleteLocalFile();
Chris@210 343 m_done = true;
Chris@210 344 m_ok = false;
Chris@210 345 m_errorString = tr("Download cancelled");
Chris@208 346 }
Chris@208 347
Chris@208 348 void
Chris@208 349 RemoteFile::done(bool error)
Chris@208 350 {
Chris@214 351 std::cerr << "RemoteFile::done(" << error << ")" << std::endl;
Chris@211 352
Chris@211 353 if (m_done) return;
Chris@211 354
Chris@208 355 emit progress(100);
Chris@210 356
Chris@208 357 if (error) {
Chris@208 358 if (m_http) {
Chris@208 359 m_errorString = m_http->errorString();
Chris@208 360 } else if (m_ftp) {
Chris@208 361 m_errorString = m_ftp->errorString();
Chris@208 362 }
Chris@208 363 }
Chris@208 364
Chris@210 365 if (m_lastStatus / 100 >= 4) {
Chris@211 366 error = true;
Chris@210 367 }
Chris@210 368
Chris@211 369 cleanup();
Chris@208 370
Chris@211 371 if (!error) {
Chris@208 372 QFileInfo fi(m_localFilename);
Chris@208 373 if (!fi.exists()) {
Chris@208 374 m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
Chris@211 375 error = true;
Chris@208 376 } else if (fi.size() == 0) {
Chris@208 377 m_errorString = tr("File contains no data!");
Chris@211 378 error = true;
Chris@208 379 }
Chris@208 380 }
Chris@211 381
Chris@211 382 if (error) {
Chris@211 383 deleteLocalFile();
Chris@211 384 }
Chris@211 385
Chris@211 386 m_ok = !error;
Chris@211 387 m_done = true;
Chris@304 388 emit ready();
Chris@211 389 }
Chris@211 390
Chris@211 391 void
Chris@211 392 RemoteFile::deleteLocalFile()
Chris@211 393 {
Chris@211 394 // std::cerr << "RemoteFile::deleteLocalFile" << std::endl;
Chris@211 395
Chris@211 396 cleanup();
Chris@211 397
Chris@211 398 if (m_localFilename == "") return;
Chris@211 399
Chris@304 400 if (m_referenced) {
Chris@304 401
Chris@304 402 QMutexLocker locker(&m_mapMutex);
Chris@304 403 m_referenced = false;
Chris@304 404
Chris@304 405 if (m_refCountMap[m_url] > 0) {
Chris@304 406 m_refCountMap[m_url]--;
Chris@304 407 if (m_refCountMap[m_url] > 0) {
Chris@304 408 m_done = true;
Chris@304 409 return;
Chris@304 410 }
Chris@304 411 }
Chris@304 412 }
Chris@304 413
Chris@211 414 m_fileCreationMutex.lock();
Chris@211 415
Chris@211 416 if (!QFile(m_localFilename).remove()) {
Chris@211 417 std::cerr << "RemoteFile::deleteLocalFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@211 418 } else {
Chris@211 419 m_localFilename = "";
Chris@211 420 }
Chris@211 421
Chris@211 422 m_fileCreationMutex.unlock();
Chris@211 423
Chris@208 424 m_done = true;
Chris@208 425 }
Chris@208 426
Chris@210 427 void
Chris@210 428 RemoteFile::showProgressDialog()
Chris@210 429 {
Chris@210 430 if (m_progressDialog) m_progressDialog->show();
Chris@210 431 }
Chris@210 432
Chris@208 433 QString
Chris@208 434 RemoteFile::createLocalFile(QUrl url)
Chris@208 435 {
Chris@208 436 QDir dir;
Chris@208 437 try {
Chris@208 438 dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
Chris@208 439 } catch (DirectoryCreationFailed f) {
Chris@208 440 std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl;
Chris@208 441 return "";
Chris@208 442 }
Chris@208 443
Chris@208 444 QString filepart = url.path().section('/', -1, -1,
Chris@208 445 QString::SectionSkipEmpty);
Chris@208 446
Chris@208 447 QString extension = filepart.section('.', -1);
Chris@208 448 QString base = filepart;
Chris@208 449 if (extension != "") {
Chris@208 450 base = base.left(base.length() - extension.length() - 1);
Chris@208 451 }
Chris@208 452 if (base == "") base = "remote";
Chris@208 453
Chris@208 454 QString filename;
Chris@208 455
Chris@208 456 if (extension == "") {
Chris@208 457 filename = base;
Chris@208 458 } else {
Chris@208 459 filename = QString("%1.%2").arg(base).arg(extension);
Chris@208 460 }
Chris@208 461
Chris@208 462 QString filepath(dir.filePath(filename));
Chris@208 463
Chris@208 464 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 465
Chris@208 466 m_fileCreationMutex.lock();
Chris@208 467 ++m_count;
Chris@208 468
Chris@208 469 if (QFileInfo(filepath).exists() ||
Chris@208 470 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 471
Chris@208 472 std::cerr << "RemoteFile::createLocalFile: Failed to create local file \""
Chris@208 473 << filepath.toStdString() << "\" for URL \""
Chris@208 474 << url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl;
Chris@208 475
Chris@208 476
Chris@208 477 if (extension == "") {
Chris@208 478 filename = QString("%1_%2").arg(base).arg(m_count);
Chris@208 479 } else {
Chris@208 480 filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
Chris@208 481 }
Chris@208 482 filepath = dir.filePath(filename);
Chris@208 483
Chris@208 484 if (QFileInfo(filepath).exists() ||
Chris@208 485 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 486
Chris@208 487 std::cerr << "RemoteFile::createLocalFile: ERROR: Failed to create local file \""
Chris@208 488 << filepath.toStdString() << "\" for URL \""
Chris@208 489 << url.toString().toStdString() << "\" (or file already exists)" << std::endl;
Chris@208 490
Chris@208 491 m_fileCreationMutex.unlock();
Chris@208 492 return "";
Chris@208 493 }
Chris@208 494 }
Chris@208 495
Chris@208 496 m_fileCreationMutex.unlock();
Chris@208 497
Chris@208 498 std::cerr << "RemoteFile::createLocalFile: url "
Chris@208 499 << url.toString().toStdString() << " -> local filename "
Chris@208 500 << filepath.toStdString() << std::endl;
Chris@208 501
Chris@208 502 return filepath;
Chris@208 503 }