annotate data/fileio/FileSource.cpp @ 985:f073d924a7c3

Fix #1058 clicking row in Layer Edit dialog when colour 3d plot layer active jumps to wrong frame (was using sample rate where resolution intended)
author Chris Cannam
date Tue, 16 Sep 2014 10:29:19 +0100 (2014-09-16)
parents c6f2b93a7d52
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