annotate data/fileio/FileSource.cpp @ 527:3c5570e3d9c5

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