annotate data/fileio/FileSource.cpp @ 706:579b2da21e7a

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