annotate data/fileio/FileSource.cpp @ 346:a9ccd644f3bf

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