annotate data/fileio/FileSource.cpp @ 661:a4faa1840384

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