annotate data/fileio/FileSource.cpp @ 470:dddd4ab77068

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