annotate data/fileio/FileSource.cpp @ 392:183ee2a55fc7

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