annotate data/fileio/FileSource.cpp @ 363:0e30c8ec15a0

* Add wave file model method for reading more than one channel at once, avoiding ludicrously expensive backward seeks and double-reads when playing multi-channel files or using them as inputs to feature extraction plugins
author Chris Cannam
date Thu, 24 Jan 2008 14:35:43 +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