annotate data/fileio/FileSource.cpp @ 398:be49bf95d4a5

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