annotate data/fileio/FileSource.cpp @ 458:f60360209e5c

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