annotate data/fileio/FileSource.cpp @ 558:1d7ebc05157e

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