annotate data/fileio/FileSource.cpp @ 497:b6dc6c7f402c

Various fixes: * Fix handling of HTTP redirects (avoiding crashes... I hope) * Fix failure to delete FFT models when a feature extraction model transformer was abandoned (also a cause of crashes in the past) * Fix deadlock when said transform was abandoned before its source model was ready because the session was being cleared (and so the source model would never be ready)
author Chris Cannam
date Fri, 28 Nov 2008 13:36:13 +0000
parents 05383ee78f3e
children bd7c46636bd0
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@317 16 #include "FileSource.h"
Chris@357 17
Chris@208 18 #include "base/TempDirectory.h"
Chris@208 19 #include "base/Exceptions.h"
Chris@392 20 #include "base/ProgressReporter.h"
Chris@208 21
Chris@208 22 #include <QHttp>
Chris@208 23 #include <QFtp>
Chris@208 24 #include <QFileInfo>
Chris@208 25 #include <QDir>
Chris@392 26 #include <QCoreApplication>
Chris@210 27 #include <QHttpResponseHeader>
Chris@208 28
Chris@208 29 #include <iostream>
Chris@405 30 #include <cstdlib>
Chris@208 31
Chris@497 32 //#define DEBUG_FILE_SOURCE 1
Chris@327 33
Chris@208 34 int
Chris@317 35 FileSource::m_count = 0;
Chris@208 36
Chris@208 37 QMutex
Chris@317 38 FileSource::m_fileCreationMutex;
Chris@208 39
Chris@317 40 FileSource::RemoteRefCountMap
Chris@317 41 FileSource::m_refCountMap;
Chris@304 42
Chris@317 43 FileSource::RemoteLocalMap
Chris@317 44 FileSource::m_remoteLocalMap;
Chris@304 45
Chris@304 46 QMutex
Chris@317 47 FileSource::m_mapMutex;
Chris@304 48
Chris@469 49 FileSource::FileSource(QString fileOrUrl, ProgressReporter *reporter) :
Chris@316 50 m_url(fileOrUrl),
Chris@316 51 m_ftp(0),
Chris@316 52 m_http(0),
Chris@316 53 m_localFile(0),
Chris@316 54 m_ok(false),
Chris@316 55 m_lastStatus(0),
Chris@316 56 m_remote(isRemote(fileOrUrl)),
Chris@316 57 m_done(false),
Chris@316 58 m_leaveLocalFile(false),
Chris@392 59 m_reporter(reporter),
Chris@316 60 m_refCounted(false)
Chris@316 61 {
Chris@327 62 #ifdef DEBUG_FILE_SOURCE
Chris@469 63 std::cerr << "FileSource::FileSource(" << fileOrUrl.toStdString() << ")" << std::endl;
Chris@327 64 #endif
Chris@316 65
Chris@316 66 if (!canHandleScheme(m_url)) {
Chris@317 67 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::endl;
Chris@316 68 m_errorString = tr("Unsupported scheme in URL");
Chris@316 69 return;
Chris@316 70 }
Chris@316 71
Chris@357 72 init();
Chris@316 73
Chris@316 74 if (isRemote() &&
Chris@316 75 (fileOrUrl.contains('%') ||
Chris@316 76 fileOrUrl.contains("--"))) { // for IDNA
Chris@316 77
Chris@316 78 waitForStatus();
Chris@316 79
Chris@316 80 if (!isAvailable()) {
Chris@336 81
Chris@316 82 // The URL was created on the assumption that the string
Chris@316 83 // was human-readable. Let's try again, this time
Chris@316 84 // assuming it was already encoded.
Chris@317 85 std::cerr << "FileSource::FileSource: Failed to retrieve URL \""
Chris@316 86 << fileOrUrl.toStdString()
Chris@316 87 << "\" as human-readable URL; "
Chris@316 88 << "trying again treating it as encoded URL"
Chris@316 89 << std::endl;
Chris@336 90
Chris@336 91 // even though our cache file doesn't exist (because the
Chris@336 92 // resource was 404), we still need to ensure we're no
Chris@336 93 // longer associating a filename with this url in the
Chris@336 94 // refcount map -- or createCacheFile will think we've
Chris@336 95 // already done all the work and no request will be sent
Chris@336 96 deleteCacheFile();
Chris@336 97
Chris@316 98 m_url.setEncodedUrl(fileOrUrl.toAscii());
Chris@336 99
Chris@336 100 m_ok = false;
Chris@336 101 m_done = false;
Chris@336 102 m_lastStatus = 0;
Chris@357 103 init();
Chris@316 104 }
Chris@316 105 }
Chris@325 106
Chris@325 107 if (!isRemote()) {
Chris@325 108 emit statusAvailable();
Chris@325 109 emit ready();
Chris@325 110 }
Chris@497 111
Chris@497 112 std::cerr << "FileSource::FileSource(string) exiting" << std::endl;
Chris@316 113 }
Chris@316 114
Chris@469 115 FileSource::FileSource(QUrl url, ProgressReporter *reporter) :
Chris@304 116 m_url(url),
Chris@208 117 m_ftp(0),
Chris@208 118 m_http(0),
Chris@208 119 m_localFile(0),
Chris@208 120 m_ok(false),
Chris@210 121 m_lastStatus(0),
Chris@316 122 m_remote(isRemote(url.toString())),
Chris@208 123 m_done(false),
Chris@316 124 m_leaveLocalFile(false),
Chris@392 125 m_reporter(reporter),
Chris@316 126 m_refCounted(false)
Chris@208 127 {
Chris@327 128 #ifdef DEBUG_FILE_SOURCE
Chris@317 129 std::cerr << "FileSource::FileSource(" << url.toString().toStdString() << ") [as url]" << std::endl;
Chris@327 130 #endif
Chris@316 131
Chris@316 132 if (!canHandleScheme(m_url)) {
Chris@317 133 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::endl;
Chris@316 134 m_errorString = tr("Unsupported scheme in URL");
Chris@208 135 return;
Chris@208 136 }
Chris@208 137
Chris@357 138 init();
Chris@497 139
Chris@497 140 std::cerr << "FileSource::FileSource(url) exiting" << std::endl;
Chris@316 141 }
Chris@304 142
Chris@317 143 FileSource::FileSource(const FileSource &rf) :
Chris@316 144 QObject(),
Chris@316 145 m_url(rf.m_url),
Chris@316 146 m_ftp(0),
Chris@316 147 m_http(0),
Chris@316 148 m_localFile(0),
Chris@316 149 m_ok(rf.m_ok),
Chris@316 150 m_lastStatus(rf.m_lastStatus),
Chris@316 151 m_remote(rf.m_remote),
Chris@316 152 m_done(false),
Chris@316 153 m_leaveLocalFile(false),
Chris@392 154 m_reporter(rf.m_reporter),
Chris@316 155 m_refCounted(false)
Chris@316 156 {
Chris@327 157 #ifdef DEBUG_FILE_SOURCE
Chris@317 158 std::cerr << "FileSource::FileSource(" << m_url.toString().toStdString() << ") [copy ctor]" << std::endl;
Chris@327 159 #endif
Chris@304 160
Chris@316 161 if (!canHandleScheme(m_url)) {
Chris@317 162 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::endl;
Chris@316 163 m_errorString = tr("Unsupported scheme in URL");
Chris@304 164 return;
Chris@304 165 }
Chris@304 166
Chris@469 167 if (!isRemote()) {
Chris@316 168 m_localFilename = rf.m_localFilename;
Chris@316 169 } else {
Chris@316 170 QMutexLocker locker(&m_mapMutex);
Chris@327 171 #ifdef DEBUG_FILE_SOURCE
Chris@317 172 std::cerr << "FileSource::FileSource(copy ctor): ref count is "
Chris@316 173 << m_refCountMap[m_url] << std::endl;
Chris@327 174 #endif
Chris@316 175 if (m_refCountMap[m_url] > 0) {
Chris@316 176 m_refCountMap[m_url]++;
Chris@327 177 #ifdef DEBUG_FILE_SOURCE
Chris@316 178 std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl;
Chris@327 179 #endif
Chris@316 180 m_localFilename = m_remoteLocalMap[m_url];
Chris@316 181 m_refCounted = true;
Chris@316 182 } else {
Chris@316 183 m_ok = false;
Chris@316 184 m_lastStatus = 404;
Chris@316 185 }
Chris@316 186 }
Chris@316 187
Chris@316 188 m_done = true;
Chris@497 189
Chris@497 190 std::cerr << "FileSource::FileSource(copy ctor) exiting" << std::endl;
Chris@316 191 }
Chris@316 192
Chris@317 193 FileSource::~FileSource()
Chris@316 194 {
Chris@327 195 #ifdef DEBUG_FILE_SOURCE
Chris@317 196 std::cerr << "FileSource(" << m_url.toString().toStdString() << ")::~FileSource" << std::endl;
Chris@327 197 #endif
Chris@316 198
Chris@316 199 cleanup();
Chris@316 200
Chris@469 201 if (isRemote() && !m_leaveLocalFile) deleteCacheFile();
Chris@316 202 }
Chris@316 203
Chris@316 204 void
Chris@357 205 FileSource::init()
Chris@316 206 {
Chris@316 207 if (!isRemote()) {
Chris@355 208 #ifdef DEBUG_FILE_SOURCE
Chris@355 209 std::cerr << "FileSource::init: Not a remote URL" << std::endl;
Chris@355 210 #endif
Chris@355 211 bool literal = false;
Chris@316 212 m_localFilename = m_url.toLocalFile();
Chris@342 213 if (m_localFilename == "") {
Chris@342 214 // QUrl may have mishandled the scheme (e.g. in a DOS path)
Chris@342 215 m_localFilename = m_url.toString();
Chris@355 216 literal = true;
Chris@342 217 }
Chris@439 218 m_localFilename = QFileInfo(m_localFilename).absoluteFilePath();
Chris@439 219
Chris@355 220 #ifdef DEBUG_FILE_SOURCE
Chris@355 221 std::cerr << "FileSource::init: URL translates to local filename \""
Chris@355 222 << m_localFilename.toStdString() << "\"" << std::endl;
Chris@355 223 #endif
Chris@316 224 m_ok = true;
Chris@355 225 m_lastStatus = 200;
Chris@355 226
Chris@316 227 if (!QFileInfo(m_localFilename).exists()) {
Chris@355 228 if (literal) {
Chris@355 229 m_lastStatus = 404;
Chris@355 230 } else {
Chris@355 231 // Again, QUrl may have been mistreating us --
Chris@355 232 // e.g. dropping a part that looks like query data
Chris@355 233 m_localFilename = m_url.toString();
Chris@355 234 literal = true;
Chris@355 235 if (!QFileInfo(m_localFilename).exists()) {
Chris@355 236 m_lastStatus = 404;
Chris@355 237 }
Chris@355 238 }
Chris@316 239 }
Chris@355 240
Chris@316 241 m_done = true;
Chris@316 242 return;
Chris@316 243 }
Chris@316 244
Chris@316 245 if (createCacheFile()) {
Chris@327 246 #ifdef DEBUG_FILE_SOURCE
Chris@469 247 std::cerr << "FileSource::init: Already have this one" << std::endl;
Chris@327 248 #endif
Chris@316 249 m_ok = true;
Chris@316 250 if (!QFileInfo(m_localFilename).exists()) {
Chris@316 251 m_lastStatus = 404;
Chris@316 252 } else {
Chris@316 253 m_lastStatus = 200;
Chris@316 254 }
Chris@316 255 m_done = true;
Chris@316 256 return;
Chris@316 257 }
Chris@316 258
Chris@208 259 if (m_localFilename == "") return;
Chris@208 260 m_localFile = new QFile(m_localFilename);
Chris@208 261 m_localFile->open(QFile::WriteOnly);
Chris@208 262
Chris@316 263 QString scheme = m_url.scheme().toLower();
Chris@316 264
Chris@327 265 #ifdef DEBUG_FILE_SOURCE
Chris@317 266 std::cerr << "FileSource::init: Don't have local copy of \""
Chris@469 267 << m_url.toString().toStdString() << "\", retrieving" << std::endl;
Chris@327 268 #endif
Chris@208 269
Chris@208 270 if (scheme == "http") {
Chris@316 271 initHttp();
Chris@357 272 std::cerr << "FileSource: initHttp succeeded" << std::endl;
Chris@208 273 } else if (scheme == "ftp") {
Chris@316 274 initFtp();
Chris@316 275 } else {
Chris@316 276 m_remote = false;
Chris@316 277 m_ok = false;
Chris@208 278 }
Chris@208 279
Chris@208 280 if (m_ok) {
Chris@316 281
Chris@316 282 QMutexLocker locker(&m_mapMutex);
Chris@316 283
Chris@316 284 if (m_refCountMap[m_url] > 0) {
Chris@316 285 // someone else has been doing the same thing at the same time,
Chris@316 286 // but has got there first
Chris@316 287 cleanup();
Chris@316 288 m_refCountMap[m_url]++;
Chris@327 289 #ifdef DEBUG_FILE_SOURCE
Chris@317 290 std::cerr << "FileSource::init: Another FileSource has got there first, abandoning our download and using theirs" << std::endl;
Chris@327 291 #endif
Chris@316 292 m_localFilename = m_remoteLocalMap[m_url];
Chris@316 293 m_refCounted = true;
Chris@316 294 m_ok = true;
Chris@316 295 if (!QFileInfo(m_localFilename).exists()) {
Chris@316 296 m_lastStatus = 404;
Chris@316 297 }
Chris@316 298 m_done = true;
Chris@316 299 return;
Chris@316 300 }
Chris@304 301
Chris@304 302 m_remoteLocalMap[m_url] = m_localFilename;
Chris@304 303 m_refCountMap[m_url]++;
Chris@316 304 m_refCounted = true;
Chris@304 305
Chris@392 306 if (m_reporter) {
Chris@392 307 m_reporter->setMessage
Chris@392 308 (tr("Downloading %1...").arg(m_url.toString()));
Chris@392 309 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@357 310 connect(this, SIGNAL(progress(int)),
Chris@392 311 m_reporter, SLOT(setProgress(int)));
Chris@316 312 }
Chris@208 313 }
Chris@208 314 }
Chris@208 315
Chris@316 316 void
Chris@317 317 FileSource::initHttp()
Chris@208 318 {
Chris@316 319 m_ok = true;
Chris@497 320 int port = m_url.port();
Chris@497 321 m_http = new QHttp(m_url.host(), port < 0 ? 80 : port);
Chris@316 322 connect(m_http, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@316 323 connect(m_http, SIGNAL(dataReadProgress(int, int)),
Chris@316 324 this, SLOT(dataReadProgress(int, int)));
Chris@316 325 connect(m_http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)),
Chris@316 326 this, SLOT(httpResponseHeaderReceived(const QHttpResponseHeader &)));
Chris@316 327
Chris@316 328 // I don't quite understand this. url.path() returns a path
Chris@316 329 // without percent encoding; for example, spaces appear as
Chris@316 330 // literal spaces. This generally won't work if sent to the
Chris@316 331 // server directly. You can retrieve a correctly encoded URL
Chris@316 332 // from QUrl using url.toEncoded(), but that gives you the
Chris@316 333 // whole URL; there doesn't seem to be any way to retrieve
Chris@316 334 // only an encoded path. Furthermore there doesn't seem to be
Chris@316 335 // any way to convert a retrieved path into an encoded path
Chris@316 336 // without explicitly specifying that you don't want the path
Chris@316 337 // separators ("/") to be encoded. (Besides being painful to
Chris@316 338 // manage, I don't see how this can work correctly in any case
Chris@316 339 // where a percent-encoded "/" is supposed to appear within a
Chris@316 340 // path element?) There also seems to be no way to retrieve
Chris@316 341 // the path plus query string, i.e. everything that I need to
Chris@316 342 // send to the HTTP server. And no way for QHttp to take a
Chris@316 343 // QUrl argument. I'm obviously missing something.
Chris@316 344
Chris@316 345 // So, two ways to do this: query the bits from the URL,
Chris@316 346 // encode them individually, and glue them back together
Chris@316 347 // again...
Chris@316 348 /*
Chris@316 349 QString path = QUrl::toPercentEncoding(m_url.path(), "/");
Chris@316 350 QList<QPair<QString, QString> > query = m_url.queryItems();
Chris@316 351 if (!query.empty()) {
Chris@316 352 QStringList q2;
Chris@316 353 for (QList<QPair<QString, QString> >::iterator i = query.begin();
Chris@316 354 i != query.end(); ++i) {
Chris@316 355 q2.push_back(QString("%1=%3")
Chris@316 356 .arg(QString(QUrl::toPercentEncoding(i->first)))
Chris@316 357 .arg(QString(QUrl::toPercentEncoding(i->second))));
Chris@316 358 }
Chris@316 359 path = QString("%1%2%3")
Chris@316 360 .arg(path).arg("?")
Chris@316 361 .arg(q2.join("&"));
Chris@316 362 }
Chris@316 363 */
Chris@316 364
Chris@316 365 // ...or, much simpler but relying on knowledge about the
Chris@316 366 // scheme://host/path/path/query etc format of the URL, we can
Chris@316 367 // get the whole URL ready-encoded and then split it on "/" as
Chris@316 368 // appropriate...
Chris@316 369
Chris@316 370 QString path = "/" + QString(m_url.toEncoded()).section('/', 3);
Chris@316 371
Chris@327 372 #ifdef DEBUG_FILE_SOURCE
Chris@317 373 std::cerr << "FileSource: path is \""
Chris@316 374 << path.toStdString() << "\"" << std::endl;
Chris@327 375 #endif
Chris@316 376
Chris@316 377 m_http->get(path, m_localFile);
Chris@316 378 }
Chris@316 379
Chris@316 380 void
Chris@317 381 FileSource::initFtp()
Chris@316 382 {
Chris@316 383 m_ok = true;
Chris@316 384 m_ftp = new QFtp;
Chris@316 385 connect(m_ftp, SIGNAL(done(bool)), this, SLOT(done(bool)));
Chris@316 386 connect(m_ftp, SIGNAL(commandFinished(int, bool)),
Chris@316 387 this, SLOT(ftpCommandFinished(int, bool)));
Chris@316 388 connect(m_ftp, SIGNAL(dataTransferProgress(qint64, qint64)),
Chris@316 389 this, SLOT(dataTransferProgress(qint64, qint64)));
Chris@316 390 m_ftp->connectToHost(m_url.host(), m_url.port(21));
Chris@316 391
Chris@316 392 QString username = m_url.userName();
Chris@316 393 if (username == "") {
Chris@316 394 username = "anonymous";
Chris@316 395 }
Chris@316 396
Chris@316 397 QString password = m_url.password();
Chris@316 398 if (password == "") {
Chris@316 399 password = QString("%1@%2").arg(getenv("USER")).arg(getenv("HOST"));
Chris@316 400 }
Chris@316 401
Chris@316 402 m_ftp->login(username, password);
Chris@316 403
Chris@316 404 QString dirpath = m_url.path().section('/', 0, -2);
Chris@316 405 QString filename = m_url.path().section('/', -1);
Chris@316 406
Chris@316 407 if (dirpath == "") dirpath = "/";
Chris@316 408 m_ftp->cd(dirpath);
Chris@316 409 m_ftp->get(filename, m_localFile);
Chris@211 410 }
Chris@211 411
Chris@211 412 void
Chris@317 413 FileSource::cleanup()
Chris@211 414 {
Chris@470 415 if (m_done) {
Chris@470 416 delete m_localFile; // does not actually delete the file
Chris@470 417 m_localFile = 0;
Chris@470 418 }
Chris@211 419 m_done = true;
Chris@214 420 if (m_http) {
Chris@287 421 QHttp *h = m_http;
Chris@214 422 m_http = 0;
Chris@287 423 h->abort();
Chris@287 424 h->deleteLater();
Chris@214 425 }
Chris@214 426 if (m_ftp) {
Chris@287 427 QFtp *f = m_ftp;
Chris@214 428 m_ftp = 0;
Chris@287 429 f->abort();
Chris@287 430 f->deleteLater();
Chris@214 431 }
Chris@470 432 if (m_localFile) {
Chris@470 433 delete m_localFile; // does not actually delete the file
Chris@470 434 m_localFile = 0;
Chris@470 435 }
Chris@208 436 }
Chris@208 437
Chris@208 438 bool
Chris@317 439 FileSource::isRemote(QString fileOrUrl)
Chris@304 440 {
Chris@342 441 // Note that a "scheme" with length 1 is probably a DOS drive letter
Chris@316 442 QString scheme = QUrl(fileOrUrl).scheme().toLower();
Chris@342 443 if (scheme == "" || scheme == "file" || scheme.length() == 1) return false;
Chris@342 444 return true;
Chris@304 445 }
Chris@304 446
Chris@304 447 bool
Chris@317 448 FileSource::canHandleScheme(QUrl url)
Chris@208 449 {
Chris@342 450 // Note that a "scheme" with length 1 is probably a DOS drive letter
Chris@208 451 QString scheme = url.scheme().toLower();
Chris@316 452 return (scheme == "http" || scheme == "ftp" ||
Chris@342 453 scheme == "file" || scheme == "" || scheme.length() == 1);
Chris@208 454 }
Chris@208 455
Chris@210 456 bool
Chris@317 457 FileSource::isAvailable()
Chris@210 458 {
Chris@316 459 waitForStatus();
Chris@211 460 bool available = true;
Chris@211 461 if (!m_ok) available = false;
Chris@211 462 else available = (m_lastStatus / 100 == 2);
Chris@327 463 #ifdef DEBUG_FILE_SOURCE
Chris@317 464 std::cerr << "FileSource::isAvailable: " << (available ? "yes" : "no")
Chris@211 465 << std::endl;
Chris@327 466 #endif
Chris@211 467 return available;
Chris@210 468 }
Chris@210 469
Chris@208 470 void
Chris@317 471 FileSource::waitForStatus()
Chris@316 472 {
Chris@316 473 while (m_ok && (!m_done && m_lastStatus == 0)) {
Chris@316 474 // std::cerr << "waitForStatus: processing (last status " << m_lastStatus << ")" << std::endl;
Chris@392 475 QCoreApplication::processEvents();
Chris@316 476 }
Chris@316 477 }
Chris@316 478
Chris@316 479 void
Chris@317 480 FileSource::waitForData()
Chris@208 481 {
Chris@211 482 while (m_ok && !m_done) {
Chris@357 483 // std::cerr << "FileSource::waitForData: calling QApplication::processEvents" << std::endl;
Chris@392 484 QCoreApplication::processEvents();
Chris@497 485 usleep(10000);
Chris@208 486 }
Chris@208 487 }
Chris@208 488
Chris@316 489 void
Chris@317 490 FileSource::setLeaveLocalFile(bool leave)
Chris@316 491 {
Chris@316 492 m_leaveLocalFile = leave;
Chris@316 493 }
Chris@316 494
Chris@208 495 bool
Chris@317 496 FileSource::isOK() const
Chris@208 497 {
Chris@208 498 return m_ok;
Chris@208 499 }
Chris@208 500
Chris@208 501 bool
Chris@317 502 FileSource::isDone() const
Chris@208 503 {
Chris@208 504 return m_done;
Chris@208 505 }
Chris@208 506
Chris@316 507 bool
Chris@317 508 FileSource::isRemote() const
Chris@316 509 {
Chris@316 510 return m_remote;
Chris@316 511 }
Chris@316 512
Chris@316 513 QString
Chris@317 514 FileSource::getLocation() const
Chris@316 515 {
Chris@316 516 return m_url.toString();
Chris@316 517 }
Chris@316 518
Chris@208 519 QString
Chris@317 520 FileSource::getLocalFilename() const
Chris@208 521 {
Chris@208 522 return m_localFilename;
Chris@208 523 }
Chris@208 524
Chris@208 525 QString
Chris@317 526 FileSource::getContentType() const
Chris@316 527 {
Chris@316 528 return m_contentType;
Chris@316 529 }
Chris@316 530
Chris@316 531 QString
Chris@317 532 FileSource::getExtension() const
Chris@316 533 {
Chris@316 534 if (m_localFilename != "") {
Chris@316 535 return QFileInfo(m_localFilename).suffix().toLower();
Chris@316 536 } else {
Chris@316 537 return QFileInfo(m_url.toLocalFile()).suffix().toLower();
Chris@316 538 }
Chris@316 539 }
Chris@316 540
Chris@316 541 QString
Chris@317 542 FileSource::getErrorString() const
Chris@208 543 {
Chris@208 544 return m_errorString;
Chris@208 545 }
Chris@208 546
Chris@208 547 void
Chris@317 548 FileSource::dataReadProgress(int done, int total)
Chris@208 549 {
Chris@208 550 dataTransferProgress(done, total);
Chris@208 551 }
Chris@208 552
Chris@208 553 void
Chris@317 554 FileSource::httpResponseHeaderReceived(const QHttpResponseHeader &resp)
Chris@210 555 {
Chris@497 556 std::cerr << "FileSource::httpResponseHeaderReceived" << std::endl;
Chris@497 557
Chris@497 558 if (resp.statusCode() / 100 == 3) {
Chris@496 559 QString location = resp.value("Location");
Chris@496 560 #ifdef DEBUG_FILE_SOURCE
Chris@496 561 std::cerr << "FileSource::responseHeaderReceived: redirect to \""
Chris@496 562 << location.toStdString() << "\" received" << std::endl;
Chris@496 563 #endif
Chris@496 564 if (location != "") {
Chris@496 565 QUrl newUrl(location);
Chris@496 566 if (newUrl != m_url) {
Chris@497 567 cleanup();
Chris@497 568 deleteCacheFile();
Chris@496 569 m_url = newUrl;
Chris@497 570 m_localFile = 0;
Chris@496 571 m_lastStatus = 0;
Chris@497 572 m_done = false;
Chris@497 573 m_refCounted = false;
Chris@496 574 init();
Chris@496 575 return;
Chris@496 576 }
Chris@496 577 }
Chris@496 578 }
Chris@497 579
Chris@497 580 m_lastStatus = resp.statusCode();
Chris@210 581 if (m_lastStatus / 100 >= 4) {
Chris@210 582 m_errorString = QString("%1 %2")
Chris@210 583 .arg(resp.statusCode()).arg(resp.reasonPhrase());
Chris@327 584 #ifdef DEBUG_FILE_SOURCE
Chris@317 585 std::cerr << "FileSource::responseHeaderReceived: "
Chris@211 586 << m_errorString.toStdString() << std::endl;
Chris@327 587 #endif
Chris@211 588 } else {
Chris@327 589 #ifdef DEBUG_FILE_SOURCE
Chris@317 590 std::cerr << "FileSource::responseHeaderReceived: "
Chris@211 591 << m_lastStatus << std::endl;
Chris@327 592 #endif
Chris@315 593 if (resp.hasContentType()) m_contentType = resp.contentType();
Chris@325 594 }
Chris@325 595 emit statusAvailable();
Chris@210 596 }
Chris@210 597
Chris@210 598 void
Chris@317 599 FileSource::ftpCommandFinished(int id, bool error)
Chris@214 600 {
Chris@327 601 #ifdef DEBUG_FILE_SOURCE
Chris@317 602 std::cerr << "FileSource::ftpCommandFinished(" << id << ", " << error << ")" << std::endl;
Chris@327 603 #endif
Chris@214 604
Chris@214 605 if (!m_ftp) return;
Chris@214 606
Chris@214 607 QFtp::Command command = m_ftp->currentCommand();
Chris@214 608
Chris@214 609 if (!error) {
Chris@327 610 #ifdef DEBUG_FILE_SOURCE
Chris@317 611 std::cerr << "FileSource::ftpCommandFinished: success for command "
Chris@214 612 << command << std::endl;
Chris@327 613 #endif
Chris@214 614 return;
Chris@214 615 }
Chris@214 616
Chris@214 617 if (command == QFtp::ConnectToHost) {
Chris@214 618 m_errorString = tr("Failed to connect to FTP server");
Chris@214 619 } else if (command == QFtp::Login) {
Chris@214 620 m_errorString = tr("Login failed");
Chris@214 621 } else if (command == QFtp::Cd) {
Chris@214 622 m_errorString = tr("Failed to change to correct directory");
Chris@214 623 } else if (command == QFtp::Get) {
Chris@214 624 m_errorString = tr("FTP download aborted");
Chris@214 625 }
Chris@214 626
Chris@214 627 m_lastStatus = 400; // for done()
Chris@214 628 }
Chris@214 629
Chris@214 630 void
Chris@317 631 FileSource::dataTransferProgress(qint64 done, qint64 total)
Chris@208 632 {
Chris@208 633 int percent = int((double(done) / double(total)) * 100.0 - 0.1);
Chris@208 634 emit progress(percent);
Chris@210 635 }
Chris@210 636
Chris@210 637 void
Chris@317 638 FileSource::cancelled()
Chris@210 639 {
Chris@210 640 m_done = true;
Chris@316 641 cleanup();
Chris@316 642
Chris@210 643 m_ok = false;
Chris@210 644 m_errorString = tr("Download cancelled");
Chris@208 645 }
Chris@208 646
Chris@208 647 void
Chris@317 648 FileSource::done(bool error)
Chris@208 649 {
Chris@327 650 emit progress(100);
Chris@327 651
Chris@327 652 #ifdef DEBUG_FILE_SOURCE
Chris@317 653 std::cerr << "FileSource::done(" << error << ")" << std::endl;
Chris@327 654 #endif
Chris@211 655
Chris@211 656 if (m_done) return;
Chris@211 657
Chris@208 658 if (error) {
Chris@208 659 if (m_http) {
Chris@208 660 m_errorString = m_http->errorString();
Chris@208 661 } else if (m_ftp) {
Chris@208 662 m_errorString = m_ftp->errorString();
Chris@208 663 }
Chris@208 664 }
Chris@208 665
Chris@210 666 if (m_lastStatus / 100 >= 4) {
Chris@211 667 error = true;
Chris@210 668 }
Chris@210 669
Chris@211 670 cleanup();
Chris@208 671
Chris@211 672 if (!error) {
Chris@208 673 QFileInfo fi(m_localFilename);
Chris@208 674 if (!fi.exists()) {
Chris@208 675 m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
Chris@211 676 error = true;
Chris@208 677 } else if (fi.size() == 0) {
Chris@208 678 m_errorString = tr("File contains no data!");
Chris@211 679 error = true;
Chris@208 680 }
Chris@208 681 }
Chris@211 682
Chris@211 683 if (error) {
Chris@327 684 #ifdef DEBUG_FILE_SOURCE
Chris@469 685 std::cerr << "FileSource::done: error is " << error << ", deleting cache file" << std::endl;
Chris@327 686 #endif
Chris@316 687 deleteCacheFile();
Chris@211 688 }
Chris@211 689
Chris@211 690 m_ok = !error;
Chris@211 691 m_done = true;
Chris@304 692 emit ready();
Chris@211 693 }
Chris@211 694
Chris@211 695 void
Chris@317 696 FileSource::deleteCacheFile()
Chris@211 697 {
Chris@327 698 #ifdef DEBUG_FILE_SOURCE
Chris@317 699 std::cerr << "FileSource::deleteCacheFile(\"" << m_localFilename.toStdString() << "\")" << std::endl;
Chris@327 700 #endif
Chris@211 701
Chris@211 702 cleanup();
Chris@211 703
Chris@316 704 if (m_localFilename == "") {
Chris@316 705 return;
Chris@316 706 }
Chris@211 707
Chris@316 708 if (!isRemote()) {
Chris@327 709 #ifdef DEBUG_FILE_SOURCE
Chris@316 710 std::cerr << "not a cache file" << std::endl;
Chris@327 711 #endif
Chris@316 712 return;
Chris@316 713 }
Chris@316 714
Chris@316 715 if (m_refCounted) {
Chris@304 716
Chris@304 717 QMutexLocker locker(&m_mapMutex);
Chris@316 718 m_refCounted = false;
Chris@304 719
Chris@304 720 if (m_refCountMap[m_url] > 0) {
Chris@304 721 m_refCountMap[m_url]--;
Chris@327 722 #ifdef DEBUG_FILE_SOURCE
Chris@316 723 std::cerr << "reduced ref count to " << m_refCountMap[m_url] << std::endl;
Chris@327 724 #endif
Chris@304 725 if (m_refCountMap[m_url] > 0) {
Chris@304 726 m_done = true;
Chris@304 727 return;
Chris@304 728 }
Chris@304 729 }
Chris@304 730 }
Chris@304 731
Chris@211 732 m_fileCreationMutex.lock();
Chris@211 733
Chris@211 734 if (!QFile(m_localFilename).remove()) {
Chris@469 735 #ifdef DEBUG_FILE_SOURCE
Chris@317 736 std::cerr << "FileSource::deleteCacheFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@469 737 #endif
Chris@211 738 } else {
Chris@327 739 #ifdef DEBUG_FILE_SOURCE
Chris@317 740 std::cerr << "FileSource::deleteCacheFile: Deleted cache file \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@327 741 #endif
Chris@211 742 m_localFilename = "";
Chris@211 743 }
Chris@211 744
Chris@211 745 m_fileCreationMutex.unlock();
Chris@211 746
Chris@208 747 m_done = true;
Chris@208 748 }
Chris@208 749
Chris@316 750 bool
Chris@317 751 FileSource::createCacheFile()
Chris@208 752 {
Chris@316 753 {
Chris@316 754 QMutexLocker locker(&m_mapMutex);
Chris@316 755
Chris@327 756 #ifdef DEBUG_FILE_SOURCE
Chris@317 757 std::cerr << "FileSource::createCacheFile: refcount is " << m_refCountMap[m_url] << std::endl;
Chris@327 758 #endif
Chris@316 759
Chris@316 760 if (m_refCountMap[m_url] > 0) {
Chris@316 761 m_refCountMap[m_url]++;
Chris@316 762 m_localFilename = m_remoteLocalMap[m_url];
Chris@327 763 #ifdef DEBUG_FILE_SOURCE
Chris@316 764 std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl;
Chris@327 765 #endif
Chris@316 766 m_refCounted = true;
Chris@316 767 return true;
Chris@316 768 }
Chris@316 769 }
Chris@316 770
Chris@208 771 QDir dir;
Chris@208 772 try {
Chris@208 773 dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
Chris@208 774 } catch (DirectoryCreationFailed f) {
Chris@327 775 #ifdef DEBUG_FILE_SOURCE
Chris@317 776 std::cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl;
Chris@327 777 #endif
Chris@208 778 return "";
Chris@208 779 }
Chris@208 780
Chris@316 781 QString filepart = m_url.path().section('/', -1, -1,
Chris@316 782 QString::SectionSkipEmpty);
Chris@208 783
Chris@457 784 QString extension = "";
Chris@457 785 if (filepart.contains('.')) extension = filepart.section('.', -1);
Chris@457 786
Chris@208 787 QString base = filepart;
Chris@208 788 if (extension != "") {
Chris@208 789 base = base.left(base.length() - extension.length() - 1);
Chris@208 790 }
Chris@208 791 if (base == "") base = "remote";
Chris@208 792
Chris@208 793 QString filename;
Chris@208 794
Chris@208 795 if (extension == "") {
Chris@208 796 filename = base;
Chris@208 797 } else {
Chris@208 798 filename = QString("%1.%2").arg(base).arg(extension);
Chris@208 799 }
Chris@208 800
Chris@208 801 QString filepath(dir.filePath(filename));
Chris@208 802
Chris@327 803 #ifdef DEBUG_FILE_SOURCE
Chris@317 804 std::cerr << "FileSource::createCacheFile: URL is \"" << m_url.toString().toStdString() << "\", dir is \"" << dir.path().toStdString() << "\", base \"" << base.toStdString() << "\", extension \"" << extension.toStdString() << "\", filebase \"" << filename.toStdString() << "\", filename \"" << filepath.toStdString() << "\"" << std::endl;
Chris@327 805 #endif
Chris@208 806
Chris@316 807 QMutexLocker fcLocker(&m_fileCreationMutex);
Chris@316 808
Chris@208 809 ++m_count;
Chris@208 810
Chris@208 811 if (QFileInfo(filepath).exists() ||
Chris@208 812 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 813
Chris@327 814 #ifdef DEBUG_FILE_SOURCE
Chris@317 815 std::cerr << "FileSource::createCacheFile: Failed to create local file \""
Chris@208 816 << filepath.toStdString() << "\" for URL \""
Chris@316 817 << m_url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl;
Chris@327 818 #endif
Chris@208 819
Chris@208 820 if (extension == "") {
Chris@208 821 filename = QString("%1_%2").arg(base).arg(m_count);
Chris@208 822 } else {
Chris@208 823 filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
Chris@208 824 }
Chris@208 825 filepath = dir.filePath(filename);
Chris@208 826
Chris@208 827 if (QFileInfo(filepath).exists() ||
Chris@208 828 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 829
Chris@327 830 #ifdef DEBUG_FILE_SOURCE
Chris@317 831 std::cerr << "FileSource::createCacheFile: ERROR: Failed to create local file \""
Chris@208 832 << filepath.toStdString() << "\" for URL \""
Chris@316 833 << m_url.toString().toStdString() << "\" (or file already exists)" << std::endl;
Chris@327 834 #endif
Chris@208 835
Chris@208 836 return "";
Chris@208 837 }
Chris@208 838 }
Chris@208 839
Chris@327 840 #ifdef DEBUG_FILE_SOURCE
Chris@317 841 std::cerr << "FileSource::createCacheFile: url "
Chris@316 842 << m_url.toString().toStdString() << " -> local filename "
Chris@316 843 << filepath.toStdString() << std::endl;
Chris@327 844 #endif
Chris@316 845
Chris@316 846 m_localFilename = filepath;
Chris@208 847
Chris@316 848 return false;
Chris@208 849 }
Chris@327 850