annotate data/fileio/FileSource.cpp @ 412:5e4238d08caa

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