annotate data/fileio/FileSource.cpp @ 360:ac300d385ab2

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