annotate data/fileio/FileSource.cpp @ 1086:9f4505ac9072

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