annotate data/fileio/FileSource.cpp @ 336:5cd7f6d10d47

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