annotate data/fileio/FileSource.cpp @ 981:c6f2b93a7d52

Respond tidily to user cancellation during download, instead of going on to attempt to load the nonexistent file as a non-audio format
author Chris Cannam
date Tue, 09 Sep 2014 16:52:24 +0100
parents 613ee830de5e
children 60e2927b1475
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@502 21 #include "system/System.h"
Chris@208 22
Chris@762 23 #include <QNetworkAccessManager>
Chris@762 24 #include <QNetworkReply>
Chris@208 25 #include <QFileInfo>
Chris@208 26 #include <QDir>
Chris@392 27 #include <QCoreApplication>
Chris@833 28 #include <QThreadStorage>
Chris@208 29
Chris@208 30 #include <iostream>
Chris@405 31 #include <cstdlib>
Chris@208 32
Chris@608 33 #include <unistd.h>
Chris@608 34
Chris@763 35 //#define DEBUG_FILE_SOURCE 1
Chris@327 36
Chris@208 37 int
Chris@317 38 FileSource::m_count = 0;
Chris@208 39
Chris@208 40 QMutex
Chris@317 41 FileSource::m_fileCreationMutex;
Chris@208 42
Chris@317 43 FileSource::RemoteRefCountMap
Chris@317 44 FileSource::m_refCountMap;
Chris@304 45
Chris@317 46 FileSource::RemoteLocalMap
Chris@317 47 FileSource::m_remoteLocalMap;
Chris@304 48
Chris@304 49 QMutex
Chris@317 50 FileSource::m_mapMutex;
Chris@304 51
Chris@529 52 #ifdef DEBUG_FILE_SOURCE
Chris@529 53 static int extantCount = 0;
Chris@529 54 static std::map<QString, int> urlExtantCountMap;
Chris@529 55 static void incCount(QString url) {
Chris@529 56 ++extantCount;
Chris@529 57 if (urlExtantCountMap.find(url) == urlExtantCountMap.end()) {
Chris@529 58 urlExtantCountMap[url] = 1;
Chris@529 59 } else {
Chris@529 60 ++urlExtantCountMap[url];
Chris@529 61 }
Chris@843 62 cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl;
Chris@529 63 }
Chris@529 64 static void decCount(QString url) {
Chris@529 65 --extantCount;
Chris@529 66 --urlExtantCountMap[url];
Chris@843 67 cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << endl;
Chris@529 68 }
Chris@529 69 #endif
Chris@529 70
Chris@833 71 static QThreadStorage<QNetworkAccessManager *> nms;
Chris@762 72
Chris@520 73 FileSource::FileSource(QString fileOrUrl, ProgressReporter *reporter,
Chris@520 74 QString preferredContentType) :
chris@831 75 m_rawFileOrUrl(fileOrUrl),
Chris@614 76 m_url(fileOrUrl, QUrl::StrictMode),
Chris@316 77 m_localFile(0),
Chris@762 78 m_reply(0),
Chris@520 79 m_preferredContentType(preferredContentType),
Chris@316 80 m_ok(false),
Chris@981 81 m_cancelled(false),
Chris@316 82 m_lastStatus(0),
Chris@706 83 m_resource(fileOrUrl.startsWith(':')),
Chris@316 84 m_remote(isRemote(fileOrUrl)),
Chris@316 85 m_done(false),
Chris@316 86 m_leaveLocalFile(false),
Chris@392 87 m_reporter(reporter),
Chris@316 88 m_refCounted(false)
Chris@316 89 {
Chris@706 90 if (m_resource) { // qrc file
Chris@706 91 m_url = QUrl("qrc" + fileOrUrl);
Chris@706 92 }
Chris@706 93
Chris@661 94 if (m_url.toString() == "") {
Chris@661 95 m_url = QUrl(fileOrUrl, QUrl::TolerantMode);
Chris@661 96 }
Chris@661 97
Chris@327 98 #ifdef DEBUG_FILE_SOURCE
Chris@843 99 cerr << "FileSource::FileSource(" << fileOrUrl << "): url <" << m_url.toString() << ">" << endl;
Chris@529 100 incCount(m_url.toString());
Chris@327 101 #endif
Chris@316 102
Chris@316 103 if (!canHandleScheme(m_url)) {
Chris@843 104 cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << endl;
Chris@316 105 m_errorString = tr("Unsupported scheme in URL");
Chris@316 106 return;
Chris@316 107 }
Chris@316 108
Chris@357 109 init();
Chris@316 110
Chris@614 111 if (!isRemote() &&
Chris@614 112 !isAvailable()) {
Chris@614 113 #ifdef DEBUG_FILE_SOURCE
Chris@843 114 cerr << "FileSource::FileSource: Failed to open local file with URL \"" << m_url.toString() << "\"; trying again assuming filename was encoded" << endl;
Chris@614 115 #endif
Chris@762 116 m_url = QUrl::fromEncoded(fileOrUrl.toLatin1());
chris@831 117 #ifdef DEBUG_FILE_SOURCE
Chris@843 118 cerr << "FileSource::FileSource: URL is now \"" << m_url.toString() << "\"" << endl;
chris@831 119 #endif
Chris@614 120 init();
Chris@614 121 }
Chris@614 122
Chris@316 123 if (isRemote() &&
Chris@316 124 (fileOrUrl.contains('%') ||
Chris@316 125 fileOrUrl.contains("--"))) { // for IDNA
Chris@316 126
Chris@316 127 waitForStatus();
Chris@316 128
Chris@316 129 if (!isAvailable()) {
Chris@336 130
Chris@316 131 // The URL was created on the assumption that the string
Chris@316 132 // was human-readable. Let's try again, this time
Chris@316 133 // assuming it was already encoded.
Chris@843 134 cerr << "FileSource::FileSource: Failed to retrieve URL \""
Chris@844 135 << fileOrUrl
Chris@316 136 << "\" as human-readable URL; "
Chris@316 137 << "trying again treating it as encoded URL"
Chris@843 138 << endl;
Chris@336 139
Chris@336 140 // even though our cache file doesn't exist (because the
Chris@336 141 // resource was 404), we still need to ensure we're no
Chris@336 142 // longer associating a filename with this url in the
Chris@336 143 // refcount map -- or createCacheFile will think we've
Chris@336 144 // already done all the work and no request will be sent
Chris@336 145 deleteCacheFile();
Chris@336 146
Chris@762 147 m_url = QUrl::fromEncoded(fileOrUrl.toLatin1());
Chris@336 148
Chris@336 149 m_ok = false;
Chris@336 150 m_done = false;
Chris@336 151 m_lastStatus = 0;
Chris@357 152 init();
Chris@316 153 }
Chris@316 154 }
Chris@325 155
Chris@325 156 if (!isRemote()) {
Chris@325 157 emit statusAvailable();
Chris@325 158 emit ready();
Chris@325 159 }
Chris@497 160
Chris@504 161 #ifdef DEBUG_FILE_SOURCE
Chris@843 162 cerr << "FileSource::FileSource(string) exiting" << endl;
Chris@504 163 #endif
Chris@316 164 }
Chris@316 165
Chris@469 166 FileSource::FileSource(QUrl url, ProgressReporter *reporter) :
Chris@304 167 m_url(url),
Chris@208 168 m_localFile(0),
Chris@762 169 m_reply(0),
Chris@208 170 m_ok(false),
Chris@981 171 m_cancelled(false),
Chris@210 172 m_lastStatus(0),
Chris@706 173 m_resource(false),
Chris@316 174 m_remote(isRemote(url.toString())),
Chris@208 175 m_done(false),
Chris@316 176 m_leaveLocalFile(false),
Chris@392 177 m_reporter(reporter),
Chris@316 178 m_refCounted(false)
Chris@208 179 {
Chris@327 180 #ifdef DEBUG_FILE_SOURCE
Chris@843 181 cerr << "FileSource::FileSource(" << url.toString() << ") [as url]" << endl;
Chris@529 182 incCount(m_url.toString());
Chris@327 183 #endif
Chris@316 184
Chris@316 185 if (!canHandleScheme(m_url)) {
Chris@843 186 cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << endl;
Chris@316 187 m_errorString = tr("Unsupported scheme in URL");
Chris@208 188 return;
Chris@208 189 }
Chris@208 190
Chris@357 191 init();
Chris@497 192
Chris@504 193 #ifdef DEBUG_FILE_SOURCE
Chris@843 194 cerr << "FileSource::FileSource(url) exiting" << endl;
Chris@504 195 #endif
Chris@316 196 }
Chris@304 197
Chris@317 198 FileSource::FileSource(const FileSource &rf) :
Chris@316 199 QObject(),
Chris@316 200 m_url(rf.m_url),
Chris@316 201 m_localFile(0),
Chris@762 202 m_reply(0),
Chris@316 203 m_ok(rf.m_ok),
Chris@981 204 m_cancelled(rf.m_cancelled),
Chris@316 205 m_lastStatus(rf.m_lastStatus),
Chris@706 206 m_resource(rf.m_resource),
Chris@316 207 m_remote(rf.m_remote),
Chris@316 208 m_done(false),
Chris@316 209 m_leaveLocalFile(false),
Chris@392 210 m_reporter(rf.m_reporter),
Chris@316 211 m_refCounted(false)
Chris@316 212 {
Chris@327 213 #ifdef DEBUG_FILE_SOURCE
Chris@843 214 cerr << "FileSource::FileSource(" << m_url.toString() << ") [copy ctor]" << endl;
Chris@529 215 incCount(m_url.toString());
Chris@327 216 #endif
Chris@304 217
Chris@316 218 if (!canHandleScheme(m_url)) {
Chris@843 219 cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << endl;
Chris@316 220 m_errorString = tr("Unsupported scheme in URL");
Chris@304 221 return;
Chris@304 222 }
Chris@304 223
Chris@469 224 if (!isRemote()) {
Chris@316 225 m_localFilename = rf.m_localFilename;
Chris@316 226 } else {
Chris@316 227 QMutexLocker locker(&m_mapMutex);
Chris@327 228 #ifdef DEBUG_FILE_SOURCE
Chris@843 229 cerr << "FileSource::FileSource(copy ctor): ref count is "
Chris@843 230 << m_refCountMap[m_url] << endl;
Chris@327 231 #endif
Chris@316 232 if (m_refCountMap[m_url] > 0) {
Chris@316 233 m_refCountMap[m_url]++;
Chris@327 234 #ifdef DEBUG_FILE_SOURCE
Chris@843 235 cerr << "raised it to " << m_refCountMap[m_url] << endl;
Chris@327 236 #endif
Chris@316 237 m_localFilename = m_remoteLocalMap[m_url];
Chris@316 238 m_refCounted = true;
Chris@316 239 } else {
Chris@316 240 m_ok = false;
Chris@316 241 m_lastStatus = 404;
Chris@316 242 }
Chris@316 243 }
Chris@316 244
Chris@316 245 m_done = true;
Chris@497 246
Chris@504 247 #ifdef DEBUG_FILE_SOURCE
Chris@843 248 cerr << "FileSource::FileSource(" << m_url.toString() << ") [copy ctor]: note: local filename is \"" << m_localFilename << "\"" << endl;
Chris@527 249 #endif
Chris@527 250
Chris@527 251 #ifdef DEBUG_FILE_SOURCE
Chris@843 252 cerr << "FileSource::FileSource(copy ctor) exiting" << endl;
Chris@504 253 #endif
Chris@316 254 }
Chris@316 255
Chris@317 256 FileSource::~FileSource()
Chris@316 257 {
Chris@327 258 #ifdef DEBUG_FILE_SOURCE
Chris@843 259 cerr << "FileSource(" << m_url.toString() << ")::~FileSource" << endl;
Chris@529 260 decCount(m_url.toString());
Chris@327 261 #endif
Chris@316 262
Chris@316 263 cleanup();
Chris@316 264
Chris@469 265 if (isRemote() && !m_leaveLocalFile) deleteCacheFile();
Chris@316 266 }
Chris@316 267
Chris@316 268 void
Chris@357 269 FileSource::init()
Chris@316 270 {
Chris@833 271 { // check we have a QNetworkAccessManager
Chris@833 272 QMutexLocker locker(&m_mapMutex);
Chris@833 273 if (!nms.hasLocalData()) {
Chris@833 274 nms.setLocalData(new QNetworkAccessManager());
Chris@833 275 }
Chris@833 276 }
Chris@833 277
Chris@706 278 if (isResource()) {
Chris@355 279 #ifdef DEBUG_FILE_SOURCE
Chris@843 280 cerr << "FileSource::init: Is a resource" << endl;
Chris@706 281 #endif
Chris@706 282 QString resourceFile = m_url.toString();
Chris@706 283 resourceFile.replace(QRegExp("^qrc:"), ":");
Chris@706 284
Chris@706 285 if (!QFileInfo(resourceFile).exists()) {
Chris@706 286 #ifdef DEBUG_FILE_SOURCE
Chris@843 287 cerr << "FileSource::init: Resource file of this name does not exist, switching to non-resource URL" << endl;
Chris@706 288 #endif
Chris@706 289 m_url = resourceFile;
Chris@706 290 m_resource = false;
Chris@706 291 }
Chris@706 292 }
Chris@706 293
Chris@706 294 if (!isRemote() && !isResource()) {
Chris@706 295 #ifdef DEBUG_FILE_SOURCE
Chris@843 296 cerr << "FileSource::init: Not a remote URL" << endl;
Chris@355 297 #endif
Chris@355 298 bool literal = false;
Chris@316 299 m_localFilename = m_url.toLocalFile();
chris@831 300
Chris@342 301 if (m_localFilename == "") {
Chris@342 302 // QUrl may have mishandled the scheme (e.g. in a DOS path)
chris@831 303 m_localFilename = m_rawFileOrUrl;
chris@831 304 #ifdef DEBUG_FILE_SOURCE
Chris@843 305 cerr << "FileSource::init: Trying literal local filename \""
Chris@843 306 << m_localFilename << "\"" << endl;
chris@831 307 #endif
Chris@355 308 literal = true;
Chris@342 309 }
Chris@439 310 m_localFilename = QFileInfo(m_localFilename).absoluteFilePath();
Chris@439 311
Chris@355 312 #ifdef DEBUG_FILE_SOURCE
Chris@843 313 cerr << "FileSource::init: URL translates to absolute filename \""
Chris@706 314 << m_localFilename << "\" (with literal=" << literal << ")"
Chris@843 315 << endl;
Chris@355 316 #endif
Chris@316 317 m_ok = true;
Chris@355 318 m_lastStatus = 200;
Chris@355 319
Chris@316 320 if (!QFileInfo(m_localFilename).exists()) {
Chris@355 321 if (literal) {
Chris@355 322 m_lastStatus = 404;
Chris@355 323 } else {
Chris@614 324 #ifdef DEBUG_FILE_SOURCE
Chris@843 325 cerr << "FileSource::init: Local file of this name does not exist, trying URL as a literal filename" << endl;
Chris@614 326 #endif
Chris@355 327 // Again, QUrl may have been mistreating us --
Chris@355 328 // e.g. dropping a part that looks like query data
chris@831 329 m_localFilename = m_rawFileOrUrl;
Chris@355 330 literal = true;
Chris@355 331 if (!QFileInfo(m_localFilename).exists()) {
Chris@355 332 m_lastStatus = 404;
Chris@355 333 }
Chris@355 334 }
Chris@316 335 }
Chris@355 336
Chris@316 337 m_done = true;
Chris@316 338 return;
Chris@316 339 }
Chris@316 340
Chris@316 341 if (createCacheFile()) {
Chris@327 342 #ifdef DEBUG_FILE_SOURCE
Chris@843 343 cerr << "FileSource::init: Already have this one" << endl;
Chris@327 344 #endif
Chris@316 345 m_ok = true;
Chris@316 346 if (!QFileInfo(m_localFilename).exists()) {
Chris@316 347 m_lastStatus = 404;
Chris@316 348 } else {
Chris@316 349 m_lastStatus = 200;
Chris@316 350 }
Chris@316 351 m_done = true;
Chris@316 352 return;
Chris@316 353 }
Chris@316 354
Chris@208 355 if (m_localFilename == "") return;
Chris@706 356
Chris@208 357 m_localFile = new QFile(m_localFilename);
Chris@208 358 m_localFile->open(QFile::WriteOnly);
Chris@208 359
Chris@706 360 if (isResource()) {
Chris@706 361
Chris@706 362 // Absent resource file case was dealt with at the top -- this
Chris@706 363 // is the successful case
Chris@706 364
Chris@706 365 QString resourceFileName = m_url.toString();
Chris@706 366 resourceFileName.replace(QRegExp("^qrc:"), ":");
Chris@706 367 QFile resourceFile(resourceFileName);
Chris@706 368 resourceFile.open(QFile::ReadOnly);
Chris@706 369 QByteArray ba(resourceFile.readAll());
Chris@706 370
Chris@706 371 #ifdef DEBUG_FILE_SOURCE
Chris@843 372 cerr << "Copying " << ba.size() << " bytes from resource file to cache file" << endl;
Chris@706 373 #endif
Chris@706 374
Chris@706 375 qint64 written = m_localFile->write(ba);
Chris@706 376 m_localFile->close();
Chris@706 377 delete m_localFile;
Chris@706 378 m_localFile = 0;
Chris@706 379
Chris@706 380 if (written != ba.size()) {
Chris@706 381 #ifdef DEBUG_FILE_SOURCE
Chris@843 382 cerr << "Copy failed (wrote " << written << " bytes)" << endl;
Chris@706 383 #endif
Chris@706 384 m_ok = false;
Chris@706 385 return;
Chris@706 386 } else {
Chris@706 387 m_ok = true;
Chris@706 388 m_lastStatus = 200;
Chris@706 389 m_done = true;
Chris@706 390 }
Chris@706 391
Chris@706 392 } else {
Chris@706 393
Chris@706 394 QString scheme = m_url.scheme().toLower();
Chris@316 395
Chris@327 396 #ifdef DEBUG_FILE_SOURCE
Chris@843 397 cerr << "FileSource::init: Don't have local copy of \""
Chris@843 398 << m_url.toString() << "\", retrieving" << endl;
Chris@327 399 #endif
Chris@208 400
Chris@762 401 if (scheme == "http" || scheme == "https" || scheme == "ftp") {
Chris@762 402 initRemote();
Chris@520 403 #ifdef DEBUG_FILE_SOURCE
Chris@843 404 cerr << "FileSource: initRemote returned" << endl;
Chris@520 405 #endif
Chris@706 406 } else {
Chris@706 407 m_remote = false;
Chris@706 408 m_ok = false;
Chris@706 409 }
Chris@208 410 }
Chris@208 411
Chris@208 412 if (m_ok) {
Chris@316 413
Chris@316 414 QMutexLocker locker(&m_mapMutex);
Chris@316 415
Chris@316 416 if (m_refCountMap[m_url] > 0) {
Chris@316 417 // someone else has been doing the same thing at the same time,
Chris@316 418 // but has got there first
Chris@316 419 cleanup();
Chris@316 420 m_refCountMap[m_url]++;
Chris@327 421 #ifdef DEBUG_FILE_SOURCE
Chris@843 422 cerr << "FileSource::init: Another FileSource has got there first, abandoning our download and using theirs" << endl;
Chris@327 423 #endif
Chris@316 424 m_localFilename = m_remoteLocalMap[m_url];
Chris@316 425 m_refCounted = true;
Chris@316 426 m_ok = true;
Chris@316 427 if (!QFileInfo(m_localFilename).exists()) {
Chris@316 428 m_lastStatus = 404;
Chris@316 429 }
Chris@316 430 m_done = true;
Chris@316 431 return;
Chris@316 432 }
Chris@304 433
Chris@304 434 m_remoteLocalMap[m_url] = m_localFilename;
Chris@304 435 m_refCountMap[m_url]++;
Chris@316 436 m_refCounted = true;
Chris@304 437
Chris@706 438 if (m_reporter && !m_done) {
Chris@392 439 m_reporter->setMessage
Chris@392 440 (tr("Downloading %1...").arg(m_url.toString()));
Chris@392 441 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@357 442 connect(this, SIGNAL(progress(int)),
Chris@392 443 m_reporter, SLOT(setProgress(int)));
Chris@316 444 }
Chris@208 445 }
Chris@208 446 }
Chris@208 447
Chris@316 448 void
Chris@762 449 FileSource::initRemote()
Chris@208 450 {
Chris@316 451 m_ok = true;
Chris@316 452
Chris@762 453 QNetworkRequest req;
Chris@762 454 req.setUrl(m_url);
Chris@762 455
Chris@762 456 if (m_preferredContentType != "") {
Chris@520 457 #ifdef DEBUG_FILE_SOURCE
Chris@843 458 cerr << "FileSource: indicating preferred content type of \""
Chris@843 459 << m_preferredContentType << "\"" << endl;
Chris@520 460 #endif
Chris@762 461 req.setRawHeader
Chris@762 462 ("Accept",
Chris@762 463 QString("%1, */*").arg(m_preferredContentType).toLatin1());
Chris@520 464 }
Chris@316 465
Chris@833 466 m_reply = nms.localData()->get(req);
Chris@762 467
Chris@762 468 connect(m_reply, SIGNAL(readyRead()),
Chris@762 469 this, SLOT(readyRead()));
Chris@762 470 connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
Chris@762 471 this, SLOT(replyFailed(QNetworkReply::NetworkError)));
Chris@762 472 connect(m_reply, SIGNAL(finished()),
Chris@762 473 this, SLOT(replyFinished()));
Chris@764 474 connect(m_reply, SIGNAL(metaDataChanged()),
Chris@764 475 this, SLOT(metaDataChanged()));
Chris@762 476 connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
Chris@762 477 this, SLOT(downloadProgress(qint64, qint64)));
Chris@211 478 }
Chris@211 479
Chris@211 480 void
Chris@317 481 FileSource::cleanup()
Chris@211 482 {
Chris@470 483 if (m_done) {
Chris@470 484 delete m_localFile; // does not actually delete the file
Chris@470 485 m_localFile = 0;
Chris@470 486 }
Chris@211 487 m_done = true;
Chris@762 488 if (m_reply) {
Chris@762 489 QNetworkReply *r = m_reply;
Chris@762 490 m_reply = 0;
Chris@913 491 // Can only call abort() when there are no errors.
Chris@913 492 if (r->error() == QNetworkReply::NoError) {
Chris@913 493 r->abort();
Chris@913 494 }
Chris@762 495 r->deleteLater();
Chris@214 496 }
Chris@470 497 if (m_localFile) {
Chris@470 498 delete m_localFile; // does not actually delete the file
Chris@470 499 m_localFile = 0;
Chris@470 500 }
Chris@208 501 }
Chris@208 502
Chris@208 503 bool
Chris@317 504 FileSource::isRemote(QString fileOrUrl)
Chris@304 505 {
Chris@342 506 // Note that a "scheme" with length 1 is probably a DOS drive letter
Chris@316 507 QString scheme = QUrl(fileOrUrl).scheme().toLower();
Chris@342 508 if (scheme == "" || scheme == "file" || scheme.length() == 1) return false;
Chris@342 509 return true;
Chris@304 510 }
Chris@304 511
Chris@304 512 bool
Chris@317 513 FileSource::canHandleScheme(QUrl url)
Chris@208 514 {
Chris@342 515 // Note that a "scheme" with length 1 is probably a DOS drive letter
Chris@208 516 QString scheme = url.scheme().toLower();
Chris@762 517 return (scheme == "http" || scheme == "https" ||
Chris@762 518 scheme == "ftp" || scheme == "file" || scheme == "qrc" ||
Chris@706 519 scheme == "" || scheme.length() == 1);
Chris@208 520 }
Chris@208 521
Chris@210 522 bool
Chris@317 523 FileSource::isAvailable()
Chris@210 524 {
Chris@316 525 waitForStatus();
Chris@211 526 bool available = true;
Chris@913 527 if (!m_ok) {
Chris@913 528 available = false;
Chris@913 529 } else {
Chris@913 530 // http 2xx status codes mean success
Chris@913 531 available = (m_lastStatus / 100 == 2);
Chris@913 532 }
Chris@327 533 #ifdef DEBUG_FILE_SOURCE
Chris@913 534 cerr << "FileSource::isAvailable: " << (available ? "yes" : "no") << endl;
Chris@327 535 #endif
Chris@211 536 return available;
Chris@210 537 }
Chris@210 538
Chris@208 539 void
Chris@317 540 FileSource::waitForStatus()
Chris@316 541 {
Chris@316 542 while (m_ok && (!m_done && m_lastStatus == 0)) {
Chris@843 543 // cerr << "waitForStatus: processing (last status " << m_lastStatus << ")" << endl;
Chris@392 544 QCoreApplication::processEvents();
Chris@316 545 }
Chris@316 546 }
Chris@316 547
Chris@316 548 void
Chris@317 549 FileSource::waitForData()
Chris@208 550 {
Chris@211 551 while (m_ok && !m_done) {
Chris@843 552 // cerr << "FileSource::waitForData: calling QApplication::processEvents" << endl;
Chris@392 553 QCoreApplication::processEvents();
Chris@497 554 usleep(10000);
Chris@208 555 }
Chris@208 556 }
Chris@208 557
Chris@316 558 void
Chris@317 559 FileSource::setLeaveLocalFile(bool leave)
Chris@316 560 {
Chris@316 561 m_leaveLocalFile = leave;
Chris@316 562 }
Chris@316 563
Chris@208 564 bool
Chris@317 565 FileSource::isOK() const
Chris@208 566 {
Chris@208 567 return m_ok;
Chris@208 568 }
Chris@208 569
Chris@208 570 bool
Chris@317 571 FileSource::isDone() const
Chris@208 572 {
Chris@208 573 return m_done;
Chris@208 574 }
Chris@208 575
Chris@316 576 bool
Chris@981 577 FileSource::wasCancelled() const
Chris@981 578 {
Chris@981 579 return m_cancelled;
Chris@981 580 }
Chris@981 581
Chris@981 582 bool
Chris@706 583 FileSource::isResource() const
Chris@706 584 {
Chris@706 585 return m_resource;
Chris@706 586 }
Chris@706 587
Chris@706 588 bool
Chris@317 589 FileSource::isRemote() const
Chris@316 590 {
Chris@316 591 return m_remote;
Chris@316 592 }
Chris@316 593
Chris@316 594 QString
Chris@317 595 FileSource::getLocation() const
Chris@316 596 {
Chris@316 597 return m_url.toString();
Chris@316 598 }
Chris@316 599
Chris@208 600 QString
Chris@317 601 FileSource::getLocalFilename() const
Chris@208 602 {
Chris@208 603 return m_localFilename;
Chris@208 604 }
Chris@208 605
Chris@208 606 QString
Chris@678 607 FileSource::getBasename() const
Chris@678 608 {
Chris@678 609 return QFileInfo(m_localFilename).fileName();
Chris@678 610 }
Chris@678 611
Chris@678 612 QString
Chris@317 613 FileSource::getContentType() const
Chris@316 614 {
Chris@316 615 return m_contentType;
Chris@316 616 }
Chris@316 617
Chris@316 618 QString
Chris@317 619 FileSource::getExtension() const
Chris@316 620 {
Chris@316 621 if (m_localFilename != "") {
Chris@316 622 return QFileInfo(m_localFilename).suffix().toLower();
Chris@316 623 } else {
Chris@316 624 return QFileInfo(m_url.toLocalFile()).suffix().toLower();
Chris@316 625 }
Chris@316 626 }
Chris@316 627
Chris@316 628 QString
Chris@317 629 FileSource::getErrorString() const
Chris@208 630 {
Chris@208 631 return m_errorString;
Chris@208 632 }
Chris@208 633
Chris@208 634 void
Chris@762 635 FileSource::readyRead()
Chris@208 636 {
Chris@762 637 m_localFile->write(m_reply->readAll());
Chris@208 638 }
Chris@208 639
Chris@208 640 void
Chris@764 641 FileSource::metaDataChanged()
Chris@210 642 {
Chris@520 643 #ifdef DEBUG_FILE_SOURCE
Chris@843 644 cerr << "FileSource::metaDataChanged" << endl;
Chris@520 645 #endif
Chris@497 646
Chris@762 647 if (!m_reply) {
Chris@843 648 cerr << "WARNING: FileSource::metaDataChanged() called without a reply object being known to us" << endl;
Chris@762 649 return;
Chris@762 650 }
Chris@762 651
Chris@913 652 // Handle http transfer status codes.
Chris@913 653
Chris@762 654 int status =
Chris@762 655 m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
Chris@762 656
Chris@913 657 // If this is a redirection (3xx) code, do the redirect
Chris@762 658 if (status / 100 == 3) {
Chris@762 659 QString location = m_reply->header
Chris@762 660 (QNetworkRequest::LocationHeader).toString();
Chris@496 661 #ifdef DEBUG_FILE_SOURCE
Chris@843 662 cerr << "FileSource::metaDataChanged: redirect to \""
Chris@843 663 << location << "\" received" << endl;
Chris@496 664 #endif
Chris@496 665 if (location != "") {
Chris@496 666 QUrl newUrl(location);
Chris@496 667 if (newUrl != m_url) {
Chris@497 668 cleanup();
Chris@497 669 deleteCacheFile();
Chris@529 670 #ifdef DEBUG_FILE_SOURCE
Chris@529 671 decCount(m_url.toString());
Chris@529 672 incCount(newUrl.toString());
Chris@529 673 #endif
Chris@496 674 m_url = newUrl;
Chris@497 675 m_localFile = 0;
Chris@496 676 m_lastStatus = 0;
Chris@497 677 m_done = false;
Chris@497 678 m_refCounted = false;
Chris@496 679 init();
Chris@496 680 return;
Chris@496 681 }
Chris@496 682 }
Chris@496 683 }
Chris@497 684
Chris@762 685 m_lastStatus = status;
Chris@913 686
Chris@913 687 // 400 and up are failures, get the error string
Chris@210 688 if (m_lastStatus / 100 >= 4) {
Chris@210 689 m_errorString = QString("%1 %2")
Chris@762 690 .arg(status)
Chris@762 691 .arg(m_reply->attribute
Chris@762 692 (QNetworkRequest::HttpReasonPhraseAttribute).toString());
Chris@327 693 #ifdef DEBUG_FILE_SOURCE
Chris@843 694 cerr << "FileSource::metaDataChanged: "
Chris@843 695 << m_errorString << endl;
Chris@327 696 #endif
Chris@211 697 } else {
Chris@327 698 #ifdef DEBUG_FILE_SOURCE
Chris@843 699 cerr << "FileSource::metaDataChanged: "
Chris@843 700 << m_lastStatus << endl;
Chris@327 701 #endif
Chris@762 702 m_contentType =
Chris@762 703 m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
Chris@325 704 }
Chris@325 705 emit statusAvailable();
Chris@210 706 }
Chris@210 707
Chris@210 708 void
Chris@762 709 FileSource::downloadProgress(qint64 done, qint64 total)
Chris@208 710 {
Chris@208 711 int percent = int((double(done) / double(total)) * 100.0 - 0.1);
Chris@208 712 emit progress(percent);
Chris@210 713 }
Chris@210 714
Chris@210 715 void
Chris@317 716 FileSource::cancelled()
Chris@210 717 {
Chris@210 718 m_done = true;
Chris@316 719 cleanup();
Chris@316 720
Chris@210 721 m_ok = false;
Chris@981 722 m_cancelled = true;
Chris@210 723 m_errorString = tr("Download cancelled");
Chris@208 724 }
Chris@208 725
Chris@208 726 void
Chris@762 727 FileSource::replyFinished()
Chris@208 728 {
Chris@327 729 emit progress(100);
Chris@327 730
Chris@327 731 #ifdef DEBUG_FILE_SOURCE
Chris@843 732 cerr << "FileSource::replyFinished()" << endl;
Chris@327 733 #endif
Chris@211 734
Chris@211 735 if (m_done) return;
Chris@211 736
Chris@913 737 QString scheme = m_url.scheme().toLower();
Chris@913 738 // For ftp transfers, replyFinished() will be called on success.
Chris@913 739 // metaDataChanged() is never called for ftp transfers.
Chris@913 740 if (scheme == "ftp") {
Chris@913 741 m_lastStatus = 200; // http ok
Chris@913 742 }
Chris@913 743
Chris@762 744 bool error = (m_lastStatus / 100 >= 4);
Chris@210 745
Chris@211 746 cleanup();
Chris@208 747
Chris@211 748 if (!error) {
Chris@208 749 QFileInfo fi(m_localFilename);
Chris@208 750 if (!fi.exists()) {
Chris@208 751 m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
Chris@211 752 error = true;
Chris@208 753 } else if (fi.size() == 0) {
Chris@208 754 m_errorString = tr("File contains no data!");
Chris@211 755 error = true;
Chris@208 756 }
Chris@208 757 }
Chris@211 758
Chris@211 759 if (error) {
Chris@327 760 #ifdef DEBUG_FILE_SOURCE
Chris@843 761 cerr << "FileSource::done: error is " << error << ", deleting cache file" << endl;
Chris@327 762 #endif
Chris@316 763 deleteCacheFile();
Chris@211 764 }
Chris@211 765
Chris@211 766 m_ok = !error;
Chris@520 767 if (m_localFile) m_localFile->flush();
Chris@211 768 m_done = true;
Chris@304 769 emit ready();
Chris@211 770 }
Chris@211 771
Chris@211 772 void
Chris@763 773 FileSource::replyFailed(QNetworkReply::NetworkError)
Chris@763 774 {
Chris@763 775 emit progress(100);
Chris@910 776 if (!m_reply) {
Chris@910 777 cerr << "WARNING: FileSource::replyFailed() called without a reply object being known to us" << endl;
Chris@910 778 } else {
Chris@910 779 m_errorString = m_reply->errorString();
Chris@910 780 }
Chris@763 781 m_ok = false;
Chris@763 782 m_done = true;
Chris@763 783 cleanup();
Chris@763 784 emit ready();
Chris@763 785 }
Chris@763 786
Chris@763 787 void
Chris@317 788 FileSource::deleteCacheFile()
Chris@211 789 {
Chris@327 790 #ifdef DEBUG_FILE_SOURCE
Chris@843 791 cerr << "FileSource::deleteCacheFile(\"" << m_localFilename << "\")" << endl;
Chris@327 792 #endif
Chris@211 793
Chris@211 794 cleanup();
Chris@211 795
Chris@316 796 if (m_localFilename == "") {
Chris@316 797 return;
Chris@316 798 }
Chris@211 799
Chris@316 800 if (!isRemote()) {
Chris@327 801 #ifdef DEBUG_FILE_SOURCE
Chris@843 802 cerr << "not a cache file" << endl;
Chris@327 803 #endif
Chris@316 804 return;
Chris@316 805 }
Chris@316 806
Chris@316 807 if (m_refCounted) {
Chris@304 808
Chris@304 809 QMutexLocker locker(&m_mapMutex);
Chris@316 810 m_refCounted = false;
Chris@304 811
Chris@304 812 if (m_refCountMap[m_url] > 0) {
Chris@304 813 m_refCountMap[m_url]--;
Chris@327 814 #ifdef DEBUG_FILE_SOURCE
Chris@843 815 cerr << "reduced ref count to " << m_refCountMap[m_url] << endl;
Chris@327 816 #endif
Chris@304 817 if (m_refCountMap[m_url] > 0) {
Chris@304 818 m_done = true;
Chris@304 819 return;
Chris@304 820 }
Chris@304 821 }
Chris@304 822 }
Chris@304 823
Chris@211 824 m_fileCreationMutex.lock();
Chris@211 825
Chris@211 826 if (!QFile(m_localFilename).remove()) {
Chris@469 827 #ifdef DEBUG_FILE_SOURCE
Chris@843 828 cerr << "FileSource::deleteCacheFile: ERROR: Failed to delete file \"" << m_localFilename << "\"" << endl;
Chris@469 829 #endif
Chris@211 830 } else {
Chris@327 831 #ifdef DEBUG_FILE_SOURCE
Chris@843 832 cerr << "FileSource::deleteCacheFile: Deleted cache file \"" << m_localFilename << "\"" << endl;
Chris@327 833 #endif
Chris@211 834 m_localFilename = "";
Chris@211 835 }
Chris@211 836
Chris@211 837 m_fileCreationMutex.unlock();
Chris@211 838
Chris@208 839 m_done = true;
Chris@208 840 }
Chris@208 841
Chris@316 842 bool
Chris@317 843 FileSource::createCacheFile()
Chris@208 844 {
Chris@316 845 {
Chris@316 846 QMutexLocker locker(&m_mapMutex);
Chris@316 847
Chris@327 848 #ifdef DEBUG_FILE_SOURCE
Chris@843 849 cerr << "FileSource::createCacheFile: refcount is " << m_refCountMap[m_url] << endl;
Chris@327 850 #endif
Chris@316 851
Chris@316 852 if (m_refCountMap[m_url] > 0) {
Chris@316 853 m_refCountMap[m_url]++;
Chris@316 854 m_localFilename = m_remoteLocalMap[m_url];
Chris@327 855 #ifdef DEBUG_FILE_SOURCE
Chris@843 856 cerr << "raised it to " << m_refCountMap[m_url] << endl;
Chris@327 857 #endif
Chris@316 858 m_refCounted = true;
Chris@316 859 return true;
Chris@316 860 }
Chris@316 861 }
Chris@316 862
Chris@208 863 QDir dir;
Chris@208 864 try {
Chris@208 865 dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
Chris@208 866 } catch (DirectoryCreationFailed f) {
Chris@327 867 #ifdef DEBUG_FILE_SOURCE
Chris@843 868 cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << endl;
Chris@327 869 #endif
Chris@962 870 return false;
Chris@208 871 }
Chris@208 872
Chris@316 873 QString filepart = m_url.path().section('/', -1, -1,
Chris@316 874 QString::SectionSkipEmpty);
Chris@208 875
Chris@457 876 QString extension = "";
Chris@457 877 if (filepart.contains('.')) extension = filepart.section('.', -1);
Chris@457 878
Chris@208 879 QString base = filepart;
Chris@208 880 if (extension != "") {
Chris@208 881 base = base.left(base.length() - extension.length() - 1);
Chris@208 882 }
Chris@208 883 if (base == "") base = "remote";
Chris@208 884
Chris@208 885 QString filename;
Chris@208 886
Chris@208 887 if (extension == "") {
Chris@208 888 filename = base;
Chris@208 889 } else {
Chris@208 890 filename = QString("%1.%2").arg(base).arg(extension);
Chris@208 891 }
Chris@208 892
Chris@208 893 QString filepath(dir.filePath(filename));
Chris@208 894
Chris@327 895 #ifdef DEBUG_FILE_SOURCE
Chris@843 896 cerr << "FileSource::createCacheFile: URL is \"" << m_url.toString() << "\", dir is \"" << dir.path() << "\", base \"" << base << "\", extension \"" << extension << "\", filebase \"" << filename << "\", filename \"" << filepath << "\"" << endl;
Chris@327 897 #endif
Chris@208 898
Chris@316 899 QMutexLocker fcLocker(&m_fileCreationMutex);
Chris@316 900
Chris@208 901 ++m_count;
Chris@208 902
Chris@208 903 if (QFileInfo(filepath).exists() ||
Chris@208 904 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 905
Chris@327 906 #ifdef DEBUG_FILE_SOURCE
Chris@843 907 cerr << "FileSource::createCacheFile: Failed to create local file \""
Chris@686 908 << filepath << "\" for URL \""
Chris@843 909 << m_url.toString() << "\" (or file already exists): appending suffix instead" << endl;
Chris@327 910 #endif
Chris@208 911
Chris@208 912 if (extension == "") {
Chris@208 913 filename = QString("%1_%2").arg(base).arg(m_count);
Chris@208 914 } else {
Chris@208 915 filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
Chris@208 916 }
Chris@208 917 filepath = dir.filePath(filename);
Chris@208 918
Chris@208 919 if (QFileInfo(filepath).exists() ||
Chris@208 920 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 921
Chris@327 922 #ifdef DEBUG_FILE_SOURCE
Chris@843 923 cerr << "FileSource::createCacheFile: ERROR: Failed to create local file \""
Chris@686 924 << filepath << "\" for URL \""
Chris@843 925 << m_url.toString() << "\" (or file already exists)" << endl;
Chris@327 926 #endif
Chris@208 927
Chris@962 928 return false;
Chris@208 929 }
Chris@208 930 }
Chris@208 931
Chris@327 932 #ifdef DEBUG_FILE_SOURCE
Chris@843 933 cerr << "FileSource::createCacheFile: url "
Chris@686 934 << m_url.toString() << " -> local filename "
Chris@843 935 << filepath << endl;
Chris@327 936 #endif
Chris@316 937
Chris@316 938 m_localFilename = filepath;
Chris@208 939
Chris@316 940 return false;
Chris@208 941 }
Chris@327 942