annotate data/fileio/FileSource.cpp @ 529:993e0a4f1343

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