annotate data/fileio/FileSource.cpp @ 501:ca208281238b

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