annotate data/fileio/FileSource.cpp @ 355:d02f71281639

* Fix #1841095 tapping time instant gives wrong time in aligned track * Fix #1815654 source tidying: Labeller * Fix (I hope) #1849999 Time value graphs one instant out
author Chris Cannam
date Thu, 13 Dec 2007 17:14:33 +0000
parents 4175a4930186
children b92513201610
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@208 17 #include "base/TempDirectory.h"
Chris@208 18 #include "base/Exceptions.h"
Chris@208 19
Chris@208 20 #include <QHttp>
Chris@208 21 #include <QFtp>
Chris@208 22 #include <QFileInfo>
Chris@208 23 #include <QDir>
Chris@208 24 #include <QApplication>
Chris@208 25 #include <QProgressDialog>
Chris@210 26 #include <QHttpResponseHeader>
Chris@208 27
Chris@208 28 #include <iostream>
Chris@208 29
Chris@355 30 #define DEBUG_FILE_SOURCE 1
Chris@327 31
Chris@208 32 int
Chris@317 33 FileSource::m_count = 0;
Chris@208 34
Chris@208 35 QMutex
Chris@317 36 FileSource::m_fileCreationMutex;
Chris@208 37
Chris@317 38 FileSource::RemoteRefCountMap
Chris@317 39 FileSource::m_refCountMap;
Chris@304 40
Chris@317 41 FileSource::RemoteLocalMap
Chris@317 42 FileSource::m_remoteLocalMap;
Chris@304 43
Chris@304 44 QMutex
Chris@317 45 FileSource::m_mapMutex;
Chris@304 46
Chris@317 47 FileSource::FileSource(QString fileOrUrl, bool showProgress) :
Chris@316 48 m_url(fileOrUrl),
Chris@316 49 m_ftp(0),
Chris@316 50 m_http(0),
Chris@316 51 m_localFile(0),
Chris@316 52 m_ok(false),
Chris@316 53 m_lastStatus(0),
Chris@316 54 m_remote(isRemote(fileOrUrl)),
Chris@316 55 m_done(false),
Chris@316 56 m_leaveLocalFile(false),
Chris@316 57 m_progressDialog(0),
Chris@316 58 m_progressShowTimer(this),
Chris@316 59 m_refCounted(false)
Chris@316 60 {
Chris@327 61 #ifdef DEBUG_FILE_SOURCE
Chris@317 62 std::cerr << "FileSource::FileSource(" << fileOrUrl.toStdString() << ")" << std::endl;
Chris@327 63 #endif
Chris@316 64
Chris@316 65 if (!canHandleScheme(m_url)) {
Chris@317 66 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::endl;
Chris@316 67 m_errorString = tr("Unsupported scheme in URL");
Chris@316 68 return;
Chris@316 69 }
Chris@316 70
Chris@316 71 init(showProgress);
Chris@316 72
Chris@316 73 if (isRemote() &&
Chris@316 74 (fileOrUrl.contains('%') ||
Chris@316 75 fileOrUrl.contains("--"))) { // for IDNA
Chris@316 76
Chris@316 77 waitForStatus();
Chris@316 78
Chris@316 79 if (!isAvailable()) {
Chris@336 80
Chris@316 81 // The URL was created on the assumption that the string
Chris@316 82 // was human-readable. Let's try again, this time
Chris@316 83 // assuming it was already encoded.
Chris@317 84 std::cerr << "FileSource::FileSource: Failed to retrieve URL \""
Chris@316 85 << fileOrUrl.toStdString()
Chris@316 86 << "\" as human-readable URL; "
Chris@316 87 << "trying again treating it as encoded URL"
Chris@316 88 << std::endl;
Chris@336 89
Chris@336 90 // even though our cache file doesn't exist (because the
Chris@336 91 // resource was 404), we still need to ensure we're no
Chris@336 92 // longer associating a filename with this url in the
Chris@336 93 // refcount map -- or createCacheFile will think we've
Chris@336 94 // already done all the work and no request will be sent
Chris@336 95 deleteCacheFile();
Chris@336 96
Chris@316 97 m_url.setEncodedUrl(fileOrUrl.toAscii());
Chris@336 98
Chris@336 99 m_ok = false;
Chris@336 100 m_done = false;
Chris@336 101 m_lastStatus = 0;
Chris@316 102 init(showProgress);
Chris@316 103 }
Chris@316 104 }
Chris@325 105
Chris@325 106 if (!isRemote()) {
Chris@325 107 emit statusAvailable();
Chris@325 108 emit ready();
Chris@325 109 }
Chris@316 110 }
Chris@316 111
Chris@317 112 FileSource::FileSource(QUrl url, bool showProgress) :
Chris@304 113 m_url(url),
Chris@208 114 m_ftp(0),
Chris@208 115 m_http(0),
Chris@208 116 m_localFile(0),
Chris@208 117 m_ok(false),
Chris@210 118 m_lastStatus(0),
Chris@316 119 m_remote(isRemote(url.toString())),
Chris@208 120 m_done(false),
Chris@316 121 m_leaveLocalFile(false),
Chris@210 122 m_progressDialog(0),
Chris@304 123 m_progressShowTimer(this),
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@316 136 init(showProgress);
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@316 150 m_progressDialog(0),
Chris@316 151 m_progressShowTimer(0),
Chris@316 152 m_refCounted(false)
Chris@316 153 {
Chris@327 154 #ifdef DEBUG_FILE_SOURCE
Chris@317 155 std::cerr << "FileSource::FileSource(" << m_url.toString().toStdString() << ") [copy ctor]" << std::endl;
Chris@327 156 #endif
Chris@304 157
Chris@316 158 if (!canHandleScheme(m_url)) {
Chris@317 159 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString().toStdString() << "\"" << std::endl;
Chris@316 160 m_errorString = tr("Unsupported scheme in URL");
Chris@304 161 return;
Chris@304 162 }
Chris@304 163
Chris@316 164 if (!isRemote()) {
Chris@316 165 m_localFilename = rf.m_localFilename;
Chris@316 166 } else {
Chris@316 167 QMutexLocker locker(&m_mapMutex);
Chris@327 168 #ifdef DEBUG_FILE_SOURCE
Chris@317 169 std::cerr << "FileSource::FileSource(copy ctor): ref count is "
Chris@316 170 << m_refCountMap[m_url] << std::endl;
Chris@327 171 #endif
Chris@316 172 if (m_refCountMap[m_url] > 0) {
Chris@316 173 m_refCountMap[m_url]++;
Chris@327 174 #ifdef DEBUG_FILE_SOURCE
Chris@316 175 std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl;
Chris@327 176 #endif
Chris@316 177 m_localFilename = m_remoteLocalMap[m_url];
Chris@316 178 m_refCounted = true;
Chris@316 179 } else {
Chris@316 180 m_ok = false;
Chris@316 181 m_lastStatus = 404;
Chris@316 182 }
Chris@316 183 }
Chris@316 184
Chris@316 185 m_done = true;
Chris@316 186 }
Chris@316 187
Chris@317 188 FileSource::~FileSource()
Chris@316 189 {
Chris@327 190 #ifdef DEBUG_FILE_SOURCE
Chris@317 191 std::cerr << "FileSource(" << m_url.toString().toStdString() << ")::~FileSource" << std::endl;
Chris@327 192 #endif
Chris@316 193
Chris@316 194 cleanup();
Chris@316 195
Chris@316 196 if (isRemote() && !m_leaveLocalFile) deleteCacheFile();
Chris@316 197 }
Chris@316 198
Chris@316 199 void
Chris@317 200 FileSource::init(bool showProgress)
Chris@316 201 {
Chris@316 202 if (!isRemote()) {
Chris@355 203 #ifdef DEBUG_FILE_SOURCE
Chris@355 204 std::cerr << "FileSource::init: Not a remote URL" << std::endl;
Chris@355 205 #endif
Chris@355 206 bool literal = false;
Chris@316 207 m_localFilename = m_url.toLocalFile();
Chris@342 208 if (m_localFilename == "") {
Chris@342 209 // QUrl may have mishandled the scheme (e.g. in a DOS path)
Chris@342 210 m_localFilename = m_url.toString();
Chris@355 211 literal = true;
Chris@342 212 }
Chris@355 213 #ifdef DEBUG_FILE_SOURCE
Chris@355 214 std::cerr << "FileSource::init: URL translates to local filename \""
Chris@355 215 << m_localFilename.toStdString() << "\"" << std::endl;
Chris@355 216 #endif
Chris@316 217 m_ok = true;
Chris@355 218 m_lastStatus = 200;
Chris@355 219
Chris@316 220 if (!QFileInfo(m_localFilename).exists()) {
Chris@355 221 if (literal) {
Chris@355 222 m_lastStatus = 404;
Chris@355 223 } else {
Chris@355 224 // Again, QUrl may have been mistreating us --
Chris@355 225 // e.g. dropping a part that looks like query data
Chris@355 226 m_localFilename = m_url.toString();
Chris@355 227 literal = true;
Chris@355 228 if (!QFileInfo(m_localFilename).exists()) {
Chris@355 229 m_lastStatus = 404;
Chris@355 230 }
Chris@355 231 }
Chris@316 232 }
Chris@355 233
Chris@316 234 m_done = true;
Chris@316 235 return;
Chris@316 236 }
Chris@316 237
Chris@316 238 if (createCacheFile()) {
Chris@327 239 #ifdef DEBUG_FILE_SOURCE
Chris@317 240 std::cerr << "FileSource::init: Already have this one" << std::endl;
Chris@327 241 #endif
Chris@316 242 m_ok = true;
Chris@316 243 if (!QFileInfo(m_localFilename).exists()) {
Chris@316 244 m_lastStatus = 404;
Chris@316 245 } else {
Chris@316 246 m_lastStatus = 200;
Chris@316 247 }
Chris@316 248 m_done = true;
Chris@316 249 return;
Chris@316 250 }
Chris@316 251
Chris@208 252 if (m_localFilename == "") return;
Chris@208 253 m_localFile = new QFile(m_localFilename);
Chris@208 254 m_localFile->open(QFile::WriteOnly);
Chris@208 255
Chris@316 256 QString scheme = m_url.scheme().toLower();
Chris@316 257
Chris@327 258 #ifdef DEBUG_FILE_SOURCE
Chris@317 259 std::cerr << "FileSource::init: Don't have local copy of \""
Chris@316 260 << m_url.toString().toStdString() << "\", retrieving" << std::endl;
Chris@327 261 #endif
Chris@208 262
Chris@208 263 if (scheme == "http") {
Chris@316 264 initHttp();
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@316 298 if (showProgress) {
Chris@316 299 m_progressDialog = new QProgressDialog(tr("Downloading %1...").arg(m_url.toString()), tr("Cancel"), 0, 100);
Chris@316 300 m_progressDialog->hide();
Chris@316 301 connect(&m_progressShowTimer, SIGNAL(timeout()),
Chris@316 302 this, SLOT(showProgressDialog()));
Chris@316 303 connect(m_progressDialog, SIGNAL(canceled()), this, SLOT(cancelled()));
Chris@316 304 m_progressShowTimer.setSingleShot(true);
Chris@316 305 m_progressShowTimer.start(2000);
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@211 408 m_done = true;
Chris@214 409 if (m_http) {
Chris@287 410 QHttp *h = m_http;
Chris@214 411 m_http = 0;
Chris@287 412 h->abort();
Chris@287 413 h->deleteLater();
Chris@214 414 }
Chris@214 415 if (m_ftp) {
Chris@287 416 QFtp *f = m_ftp;
Chris@214 417 m_ftp = 0;
Chris@287 418 f->abort();
Chris@287 419 f->deleteLater();
Chris@214 420 }
Chris@211 421 delete m_progressDialog;
Chris@211 422 m_progressDialog = 0;
Chris@304 423 delete m_localFile; // does not actually delete the file
Chris@211 424 m_localFile = 0;
Chris@208 425 }
Chris@208 426
Chris@208 427 bool
Chris@317 428 FileSource::isRemote(QString fileOrUrl)
Chris@304 429 {
Chris@342 430 // Note that a "scheme" with length 1 is probably a DOS drive letter
Chris@316 431 QString scheme = QUrl(fileOrUrl).scheme().toLower();
Chris@342 432 if (scheme == "" || scheme == "file" || scheme.length() == 1) return false;
Chris@342 433 return true;
Chris@304 434 }
Chris@304 435
Chris@304 436 bool
Chris@317 437 FileSource::canHandleScheme(QUrl url)
Chris@208 438 {
Chris@342 439 // Note that a "scheme" with length 1 is probably a DOS drive letter
Chris@208 440 QString scheme = url.scheme().toLower();
Chris@316 441 return (scheme == "http" || scheme == "ftp" ||
Chris@342 442 scheme == "file" || scheme == "" || scheme.length() == 1);
Chris@208 443 }
Chris@208 444
Chris@210 445 bool
Chris@317 446 FileSource::isAvailable()
Chris@210 447 {
Chris@316 448 waitForStatus();
Chris@211 449 bool available = true;
Chris@211 450 if (!m_ok) available = false;
Chris@211 451 else available = (m_lastStatus / 100 == 2);
Chris@327 452 #ifdef DEBUG_FILE_SOURCE
Chris@317 453 std::cerr << "FileSource::isAvailable: " << (available ? "yes" : "no")
Chris@211 454 << std::endl;
Chris@327 455 #endif
Chris@211 456 return available;
Chris@210 457 }
Chris@210 458
Chris@208 459 void
Chris@317 460 FileSource::waitForStatus()
Chris@316 461 {
Chris@316 462 while (m_ok && (!m_done && m_lastStatus == 0)) {
Chris@316 463 // std::cerr << "waitForStatus: processing (last status " << m_lastStatus << ")" << std::endl;
Chris@316 464 QApplication::processEvents();
Chris@316 465 }
Chris@316 466 }
Chris@316 467
Chris@316 468 void
Chris@317 469 FileSource::waitForData()
Chris@208 470 {
Chris@211 471 while (m_ok && !m_done) {
Chris@208 472 QApplication::processEvents();
Chris@208 473 }
Chris@208 474 }
Chris@208 475
Chris@316 476 void
Chris@317 477 FileSource::setLeaveLocalFile(bool leave)
Chris@316 478 {
Chris@316 479 m_leaveLocalFile = leave;
Chris@316 480 }
Chris@316 481
Chris@208 482 bool
Chris@317 483 FileSource::isOK() const
Chris@208 484 {
Chris@208 485 return m_ok;
Chris@208 486 }
Chris@208 487
Chris@208 488 bool
Chris@317 489 FileSource::isDone() const
Chris@208 490 {
Chris@208 491 return m_done;
Chris@208 492 }
Chris@208 493
Chris@316 494 bool
Chris@317 495 FileSource::isRemote() const
Chris@316 496 {
Chris@316 497 return m_remote;
Chris@316 498 }
Chris@316 499
Chris@316 500 QString
Chris@317 501 FileSource::getLocation() const
Chris@316 502 {
Chris@316 503 return m_url.toString();
Chris@316 504 }
Chris@316 505
Chris@208 506 QString
Chris@317 507 FileSource::getLocalFilename() const
Chris@208 508 {
Chris@208 509 return m_localFilename;
Chris@208 510 }
Chris@208 511
Chris@208 512 QString
Chris@317 513 FileSource::getContentType() const
Chris@316 514 {
Chris@316 515 return m_contentType;
Chris@316 516 }
Chris@316 517
Chris@316 518 QString
Chris@317 519 FileSource::getExtension() const
Chris@316 520 {
Chris@316 521 if (m_localFilename != "") {
Chris@316 522 return QFileInfo(m_localFilename).suffix().toLower();
Chris@316 523 } else {
Chris@316 524 return QFileInfo(m_url.toLocalFile()).suffix().toLower();
Chris@316 525 }
Chris@316 526 }
Chris@316 527
Chris@316 528 QString
Chris@317 529 FileSource::getErrorString() const
Chris@208 530 {
Chris@208 531 return m_errorString;
Chris@208 532 }
Chris@208 533
Chris@208 534 void
Chris@317 535 FileSource::dataReadProgress(int done, int total)
Chris@208 536 {
Chris@208 537 dataTransferProgress(done, total);
Chris@208 538 }
Chris@208 539
Chris@208 540 void
Chris@317 541 FileSource::httpResponseHeaderReceived(const QHttpResponseHeader &resp)
Chris@210 542 {
Chris@210 543 m_lastStatus = resp.statusCode();
Chris@210 544 if (m_lastStatus / 100 >= 4) {
Chris@210 545 m_errorString = QString("%1 %2")
Chris@210 546 .arg(resp.statusCode()).arg(resp.reasonPhrase());
Chris@327 547 #ifdef DEBUG_FILE_SOURCE
Chris@317 548 std::cerr << "FileSource::responseHeaderReceived: "
Chris@211 549 << m_errorString.toStdString() << std::endl;
Chris@327 550 #endif
Chris@211 551 } else {
Chris@327 552 #ifdef DEBUG_FILE_SOURCE
Chris@317 553 std::cerr << "FileSource::responseHeaderReceived: "
Chris@211 554 << m_lastStatus << std::endl;
Chris@327 555 #endif
Chris@315 556 if (resp.hasContentType()) m_contentType = resp.contentType();
Chris@325 557 }
Chris@325 558 emit statusAvailable();
Chris@210 559 }
Chris@210 560
Chris@210 561 void
Chris@317 562 FileSource::ftpCommandFinished(int id, bool error)
Chris@214 563 {
Chris@327 564 #ifdef DEBUG_FILE_SOURCE
Chris@317 565 std::cerr << "FileSource::ftpCommandFinished(" << id << ", " << error << ")" << std::endl;
Chris@327 566 #endif
Chris@214 567
Chris@214 568 if (!m_ftp) return;
Chris@214 569
Chris@214 570 QFtp::Command command = m_ftp->currentCommand();
Chris@214 571
Chris@214 572 if (!error) {
Chris@327 573 #ifdef DEBUG_FILE_SOURCE
Chris@317 574 std::cerr << "FileSource::ftpCommandFinished: success for command "
Chris@214 575 << command << std::endl;
Chris@327 576 #endif
Chris@214 577 return;
Chris@214 578 }
Chris@214 579
Chris@214 580 if (command == QFtp::ConnectToHost) {
Chris@214 581 m_errorString = tr("Failed to connect to FTP server");
Chris@214 582 } else if (command == QFtp::Login) {
Chris@214 583 m_errorString = tr("Login failed");
Chris@214 584 } else if (command == QFtp::Cd) {
Chris@214 585 m_errorString = tr("Failed to change to correct directory");
Chris@214 586 } else if (command == QFtp::Get) {
Chris@214 587 m_errorString = tr("FTP download aborted");
Chris@214 588 }
Chris@214 589
Chris@214 590 m_lastStatus = 400; // for done()
Chris@214 591 }
Chris@214 592
Chris@214 593 void
Chris@317 594 FileSource::dataTransferProgress(qint64 done, qint64 total)
Chris@208 595 {
Chris@208 596 int percent = int((double(done) / double(total)) * 100.0 - 0.1);
Chris@208 597 emit progress(percent);
Chris@208 598
Chris@327 599 if (!m_progressDialog) return;
Chris@327 600
Chris@211 601 if (percent > 0) {
Chris@211 602 m_progressDialog->setValue(percent);
Chris@211 603 m_progressDialog->show();
Chris@211 604 }
Chris@210 605 }
Chris@210 606
Chris@210 607 void
Chris@317 608 FileSource::cancelled()
Chris@210 609 {
Chris@210 610 m_done = true;
Chris@316 611 cleanup();
Chris@316 612
Chris@210 613 m_ok = false;
Chris@210 614 m_errorString = tr("Download cancelled");
Chris@208 615 }
Chris@208 616
Chris@208 617 void
Chris@317 618 FileSource::done(bool error)
Chris@208 619 {
Chris@327 620 emit progress(100);
Chris@327 621
Chris@327 622 #ifdef DEBUG_FILE_SOURCE
Chris@317 623 std::cerr << "FileSource::done(" << error << ")" << std::endl;
Chris@327 624 #endif
Chris@211 625
Chris@211 626 if (m_done) return;
Chris@211 627
Chris@208 628 if (error) {
Chris@208 629 if (m_http) {
Chris@208 630 m_errorString = m_http->errorString();
Chris@208 631 } else if (m_ftp) {
Chris@208 632 m_errorString = m_ftp->errorString();
Chris@208 633 }
Chris@208 634 }
Chris@208 635
Chris@210 636 if (m_lastStatus / 100 >= 4) {
Chris@211 637 error = true;
Chris@210 638 }
Chris@210 639
Chris@211 640 cleanup();
Chris@208 641
Chris@211 642 if (!error) {
Chris@208 643 QFileInfo fi(m_localFilename);
Chris@208 644 if (!fi.exists()) {
Chris@208 645 m_errorString = tr("Failed to create local file %1").arg(m_localFilename);
Chris@211 646 error = true;
Chris@208 647 } else if (fi.size() == 0) {
Chris@208 648 m_errorString = tr("File contains no data!");
Chris@211 649 error = true;
Chris@208 650 }
Chris@208 651 }
Chris@211 652
Chris@211 653 if (error) {
Chris@327 654 #ifdef DEBUG_FILE_SOURCE
Chris@317 655 std::cerr << "FileSource::done: error is " << error << ", deleting cache file" << std::endl;
Chris@327 656 #endif
Chris@316 657 deleteCacheFile();
Chris@211 658 }
Chris@211 659
Chris@211 660 m_ok = !error;
Chris@211 661 m_done = true;
Chris@304 662 emit ready();
Chris@211 663 }
Chris@211 664
Chris@211 665 void
Chris@317 666 FileSource::deleteCacheFile()
Chris@211 667 {
Chris@327 668 #ifdef DEBUG_FILE_SOURCE
Chris@317 669 std::cerr << "FileSource::deleteCacheFile(\"" << m_localFilename.toStdString() << "\")" << std::endl;
Chris@327 670 #endif
Chris@211 671
Chris@211 672 cleanup();
Chris@211 673
Chris@316 674 if (m_localFilename == "") {
Chris@316 675 return;
Chris@316 676 }
Chris@211 677
Chris@316 678 if (!isRemote()) {
Chris@327 679 #ifdef DEBUG_FILE_SOURCE
Chris@316 680 std::cerr << "not a cache file" << std::endl;
Chris@327 681 #endif
Chris@316 682 return;
Chris@316 683 }
Chris@316 684
Chris@316 685 if (m_refCounted) {
Chris@304 686
Chris@304 687 QMutexLocker locker(&m_mapMutex);
Chris@316 688 m_refCounted = false;
Chris@304 689
Chris@304 690 if (m_refCountMap[m_url] > 0) {
Chris@304 691 m_refCountMap[m_url]--;
Chris@327 692 #ifdef DEBUG_FILE_SOURCE
Chris@316 693 std::cerr << "reduced ref count to " << m_refCountMap[m_url] << std::endl;
Chris@327 694 #endif
Chris@304 695 if (m_refCountMap[m_url] > 0) {
Chris@304 696 m_done = true;
Chris@304 697 return;
Chris@304 698 }
Chris@304 699 }
Chris@304 700 }
Chris@304 701
Chris@211 702 m_fileCreationMutex.lock();
Chris@211 703
Chris@211 704 if (!QFile(m_localFilename).remove()) {
Chris@327 705 #ifdef DEBUG_FILE_SOURCE
Chris@317 706 std::cerr << "FileSource::deleteCacheFile: ERROR: Failed to delete file \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@327 707 #endif
Chris@211 708 } else {
Chris@327 709 #ifdef DEBUG_FILE_SOURCE
Chris@317 710 std::cerr << "FileSource::deleteCacheFile: Deleted cache file \"" << m_localFilename.toStdString() << "\"" << std::endl;
Chris@327 711 #endif
Chris@211 712 m_localFilename = "";
Chris@211 713 }
Chris@211 714
Chris@211 715 m_fileCreationMutex.unlock();
Chris@211 716
Chris@208 717 m_done = true;
Chris@208 718 }
Chris@208 719
Chris@210 720 void
Chris@317 721 FileSource::showProgressDialog()
Chris@210 722 {
Chris@210 723 if (m_progressDialog) m_progressDialog->show();
Chris@210 724 }
Chris@210 725
Chris@316 726 bool
Chris@317 727 FileSource::createCacheFile()
Chris@208 728 {
Chris@316 729 {
Chris@316 730 QMutexLocker locker(&m_mapMutex);
Chris@316 731
Chris@327 732 #ifdef DEBUG_FILE_SOURCE
Chris@317 733 std::cerr << "FileSource::createCacheFile: refcount is " << m_refCountMap[m_url] << std::endl;
Chris@327 734 #endif
Chris@316 735
Chris@316 736 if (m_refCountMap[m_url] > 0) {
Chris@316 737 m_refCountMap[m_url]++;
Chris@316 738 m_localFilename = m_remoteLocalMap[m_url];
Chris@327 739 #ifdef DEBUG_FILE_SOURCE
Chris@316 740 std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl;
Chris@327 741 #endif
Chris@316 742 m_refCounted = true;
Chris@316 743 return true;
Chris@316 744 }
Chris@316 745 }
Chris@316 746
Chris@208 747 QDir dir;
Chris@208 748 try {
Chris@208 749 dir = TempDirectory::getInstance()->getSubDirectoryPath("download");
Chris@208 750 } catch (DirectoryCreationFailed f) {
Chris@327 751 #ifdef DEBUG_FILE_SOURCE
Chris@317 752 std::cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl;
Chris@327 753 #endif
Chris@208 754 return "";
Chris@208 755 }
Chris@208 756
Chris@316 757 QString filepart = m_url.path().section('/', -1, -1,
Chris@316 758 QString::SectionSkipEmpty);
Chris@208 759
Chris@208 760 QString extension = filepart.section('.', -1);
Chris@208 761 QString base = filepart;
Chris@208 762 if (extension != "") {
Chris@208 763 base = base.left(base.length() - extension.length() - 1);
Chris@208 764 }
Chris@208 765 if (base == "") base = "remote";
Chris@208 766
Chris@208 767 QString filename;
Chris@208 768
Chris@208 769 if (extension == "") {
Chris@208 770 filename = base;
Chris@208 771 } else {
Chris@208 772 filename = QString("%1.%2").arg(base).arg(extension);
Chris@208 773 }
Chris@208 774
Chris@208 775 QString filepath(dir.filePath(filename));
Chris@208 776
Chris@327 777 #ifdef DEBUG_FILE_SOURCE
Chris@317 778 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 779 #endif
Chris@208 780
Chris@316 781 QMutexLocker fcLocker(&m_fileCreationMutex);
Chris@316 782
Chris@208 783 ++m_count;
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: Failed to create local file \""
Chris@208 790 << filepath.toStdString() << "\" for URL \""
Chris@316 791 << m_url.toString().toStdString() << "\" (or file already exists): appending suffix instead" << std::endl;
Chris@327 792 #endif
Chris@208 793
Chris@208 794 if (extension == "") {
Chris@208 795 filename = QString("%1_%2").arg(base).arg(m_count);
Chris@208 796 } else {
Chris@208 797 filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension);
Chris@208 798 }
Chris@208 799 filepath = dir.filePath(filename);
Chris@208 800
Chris@208 801 if (QFileInfo(filepath).exists() ||
Chris@208 802 !QFile(filepath).open(QFile::WriteOnly)) {
Chris@208 803
Chris@327 804 #ifdef DEBUG_FILE_SOURCE
Chris@317 805 std::cerr << "FileSource::createCacheFile: ERROR: Failed to create local file \""
Chris@208 806 << filepath.toStdString() << "\" for URL \""
Chris@316 807 << m_url.toString().toStdString() << "\" (or file already exists)" << std::endl;
Chris@327 808 #endif
Chris@208 809
Chris@208 810 return "";
Chris@208 811 }
Chris@208 812 }
Chris@208 813
Chris@327 814 #ifdef DEBUG_FILE_SOURCE
Chris@317 815 std::cerr << "FileSource::createCacheFile: url "
Chris@316 816 << m_url.toString().toStdString() << " -> local filename "
Chris@316 817 << filepath.toStdString() << std::endl;
Chris@327 818 #endif
Chris@316 819
Chris@316 820 m_localFilename = filepath;
Chris@208 821
Chris@316 822 return false;
Chris@208 823 }
Chris@327 824
Chris@327 825 FileSourceProgressPrinter::FileSourceProgressPrinter() :
Chris@327 826 m_lastProgress(0)
Chris@327 827 {
Chris@327 828 }
Chris@327 829
Chris@327 830 FileSourceProgressPrinter::~FileSourceProgressPrinter()
Chris@327 831 {
Chris@327 832 if (m_lastProgress > 0 && m_lastProgress != 100) {
Chris@327 833 std::cerr << "\r\n";
Chris@327 834 }
Chris@327 835 }
Chris@327 836
Chris@327 837 void
Chris@327 838 FileSourceProgressPrinter::progress(int progress)
Chris@327 839 {
Chris@327 840 if (progress == m_lastProgress) return;
Chris@327 841 if (progress == 100) std::cerr << "\r\n";
Chris@327 842 else std::cerr << "\r" << progress << "%";
Chris@327 843 m_lastProgress = progress;
Chris@327 844 }
Chris@327 845