annotate data/fileio/FileSource.cpp @ 469:a8a7b8f698c8

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