1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ 2 3 /* 4 Rosegarden 5 A MIDI and audio sequencer and musical notation editor. 6 Copyright 2000-2021 the Rosegarden development team. 7 8 This file originally from Sonic Visualiser, copyright 2007 Queen 9 Mary, University of London. 10 11 This program is free software; you can redistribute it and/or 12 modify it under the terms of the GNU General Public License as 13 published by the Free Software Foundation; either version 2 of the 14 License, or (at your option) any later version. See the file 15 COPYING included with this distribution for more information. 16 */ 17 18 #include "FileSource.h" 19 20 #include "TempDirectory.h" 21 #include "misc/Strings.h" 22 23 #include <QNetworkAccessManager> 24 #include <QNetworkReply> 25 #include <QFileInfo> 26 #include <QDir> 27 #include <QCoreApplication> 28 #include <QThreadStorage> 29 #include <QRegularExpression> 30 31 #include <iostream> 32 #include <cstdlib> 33 34 #include <unistd.h> 35 36 //#define DEBUG_FILE_SOURCE 1 37 38 namespace Rosegarden { 39 40 int 41 FileSource::m_count = 0; 42 43 QMutex 44 FileSource::m_fileCreationMutex; 45 46 FileSource::RemoteRefCountMap 47 FileSource::m_refCountMap; 48 49 FileSource::RemoteLocalMap 50 FileSource::m_remoteLocalMap; 51 52 QMutex 53 FileSource::m_mapMutex; 54 55 #ifdef DEBUG_FILE_SOURCE 56 static int extantCount = 0; 57 static std::map<QString, int> urlExtantCountMap; 58 static void incCount(QString url) { 59 ++extantCount; 60 if (urlExtantCountMap.find(url) == urlExtantCountMap.end()) { 61 urlExtantCountMap[url] = 1; 62 } else { 63 ++urlExtantCountMap[url]; 64 } 65 std::cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << std::endl; 66 } 67 static void decCount(QString url) { 68 --extantCount; 69 --urlExtantCountMap[url]; 70 std::cerr << "FileSource: Now " << urlExtantCountMap[url] << " for this url, " << extantCount << " total" << std::endl; 71 } 72 #endif 73 74 static QThreadStorage<QNetworkAccessManager *> nms; 75 76 #if 0 77 FileSource::FileSource(QString fileOrUrl, 78 QString preferredContentType) : 79 m_rawFileOrUrl(fileOrUrl), 80 m_url(fileOrUrl, QUrl::StrictMode), 81 m_localFile(0), 82 m_reply(0), 83 m_preferredContentType(preferredContentType), 84 m_ok(false), 85 m_lastStatus(0), 86 m_resource(fileOrUrl.startsWith(':')), 87 m_remote(isRemote(fileOrUrl)), 88 m_done(false), 89 m_leaveLocalFile(false), 90 m_refCounted(false) 91 { 92 if (m_resource) { // qrc file 93 m_url = QUrl("qrc" + fileOrUrl); 94 } 95 96 if (m_url.toString() == "") { 97 m_url = QUrl(fileOrUrl, QUrl::TolerantMode); 98 } 99 100 #ifdef DEBUG_FILE_SOURCE 101 std::cerr << "FileSource::FileSource(" << fileOrUrl << "): url <" << m_url.toString() << ">" << std::endl; 102 incCount(m_url.toString()); 103 #endif 104 105 if (!canHandleScheme(m_url)) { 106 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << std::endl; 107 m_errorString = tr("Unsupported scheme in URL"); 108 return; 109 } 110 111 init(); 112 113 if (!isRemote() && 114 !isAvailable()) { 115 #ifdef DEBUG_FILE_SOURCE 116 std::cerr << "FileSource::FileSource: Failed to open local file with URL \"" << m_url.toString() << "\"; trying again assuming filename was encoded" << std::endl; 117 #endif 118 m_url = QUrl::fromEncoded(fileOrUrl.toLatin1()); 119 #ifdef DEBUG_FILE_SOURCE 120 std::cerr << "FileSource::FileSource: URL is now \"" << m_url.toString() << "\"" << std::endl; 121 #endif 122 init(); 123 } 124 125 if (isRemote() && 126 (fileOrUrl.contains('%') || 127 fileOrUrl.contains("--"))) { // for IDNA 128 129 waitForStatus(); 130 131 if (!isAvailable()) { 132 133 // The URL was created on the assumption that the string 134 // was human-readable. Let's try again, this time 135 // assuming it was already encoded. 136 std::cerr << "FileSource::FileSource: Failed to retrieve URL \"" 137 << fileOrUrl 138 << "\" as human-readable URL; " 139 << "trying again treating it as encoded URL" 140 << std::endl; 141 142 // even though our cache file doesn't exist (because the 143 // resource was 404), we still need to ensure we're no 144 // longer associating a filename with this url in the 145 // refcount map -- or createCacheFile will think we've 146 // already done all the work and no request will be sent 147 deleteCacheFile(); 148 149 m_url = QUrl::fromEncoded(fileOrUrl.toLatin1()); 150 151 m_ok = false; 152 m_done = false; 153 m_lastStatus = 0; 154 init(); 155 } 156 } 157 158 if (!isRemote()) { 159 emit statusAvailable(); 160 emit ready(); 161 } 162 163 #ifdef DEBUG_FILE_SOURCE 164 std::cerr << "FileSource::FileSource(string) exiting" << std::endl; 165 #endif 166 } 167 #endif 168 169 FileSource::FileSource(QUrl url) : 170 m_url(url), 171 m_localFile(nullptr), 172 m_reply(nullptr), 173 m_ok(false), 174 m_lastStatus(0), 175 m_resource(false), 176 m_remote(isRemote(url.toString())), 177 m_done(false), 178 m_leaveLocalFile(false), 179 m_refCounted(false) 180 { 181 #ifdef DEBUG_FILE_SOURCE 182 std::cerr << "FileSource::FileSource(" << url.toString() << ") [as url]" << std::endl; 183 incCount(m_url.toString()); 184 #endif 185 186 if (!canHandleScheme(m_url)) { 187 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << std::endl; 188 m_errorString = tr("Unsupported scheme in URL"); 189 return; 190 } 191 192 init(); 193 194 #ifdef DEBUG_FILE_SOURCE 195 std::cerr << "FileSource::FileSource(url) exiting" << std::endl; 196 #endif 197 } 198 199 FileSource::FileSource(const FileSource &rf) : 200 QObject(), 201 m_url(rf.m_url), 202 m_localFile(nullptr), 203 m_reply(nullptr), 204 m_ok(rf.m_ok), 205 m_lastStatus(rf.m_lastStatus), 206 m_resource(rf.m_resource), 207 m_remote(rf.m_remote), 208 m_done(false), 209 m_leaveLocalFile(false), 210 m_refCounted(false) 211 { 212 #ifdef DEBUG_FILE_SOURCE 213 std::cerr << "FileSource::FileSource(" << m_url.toString() << ") [copy ctor]" << std::endl; 214 incCount(m_url.toString()); 215 #endif 216 217 if (!canHandleScheme(m_url)) { 218 std::cerr << "FileSource::FileSource: ERROR: Unsupported scheme in URL \"" << m_url.toString() << "\"" << std::endl; 219 m_errorString = tr("Unsupported scheme in URL"); 220 return; 221 } 222 223 if (!isRemote()) { 224 m_localFilename = rf.m_localFilename; 225 } else { 226 QMutexLocker locker(&m_mapMutex); 227 #ifdef DEBUG_FILE_SOURCE 228 std::cerr << "FileSource::FileSource(copy ctor): ref count is " 229 << m_refCountMap[m_url] << std::endl; 230 #endif 231 if (m_refCountMap[m_url] > 0) { 232 m_refCountMap[m_url]++; 233 #ifdef DEBUG_FILE_SOURCE 234 std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl; 235 #endif 236 m_localFilename = m_remoteLocalMap[m_url]; 237 m_refCounted = true; 238 } else { 239 m_ok = false; 240 m_lastStatus = 404; 241 } 242 } 243 244 m_done = true; 245 246 #ifdef DEBUG_FILE_SOURCE 247 std::cerr << "FileSource::FileSource(" << m_url.toString() << ") [copy ctor]: note: local filename is \"" << m_localFilename << "\"" << std::endl; 248 #endif 249 250 #ifdef DEBUG_FILE_SOURCE 251 std::cerr << "FileSource::FileSource(copy ctor) exiting" << std::endl; 252 #endif 253 } 254 255 FileSource::~FileSource() 256 { 257 #ifdef DEBUG_FILE_SOURCE 258 std::cerr << "FileSource(" << m_url.toString() << ")::~FileSource" << std::endl; 259 decCount(m_url.toString()); 260 #endif 261 262 cleanup(); 263 264 if (isRemote() && !m_leaveLocalFile) deleteCacheFile(); 265 } 266 267 void 268 FileSource::init() 269 { 270 { // check we have a QNetworkAccessManager 271 QMutexLocker locker(&m_mapMutex); 272 if (!nms.hasLocalData()) { 273 nms.setLocalData(new QNetworkAccessManager()); 274 } 275 } 276 277 if (isResource()) { 278 #ifdef DEBUG_FILE_SOURCE 279 std::cerr << "FileSource::init: Is a resource" << std::endl; 280 #endif 281 QString resourceFile = m_url.toString(); 282 resourceFile.replace(QRegularExpression("^qrc:"), ":"); 283 284 if (!QFileInfo(resourceFile).exists()) { 285 #ifdef DEBUG_FILE_SOURCE 286 std::cerr << "FileSource::init: Resource file of this name does not exist, switching to non-resource URL" << std::endl; 287 #endif 288 m_url = QUrl(resourceFile); 289 m_resource = false; 290 } 291 } 292 293 if (!isRemote() && !isResource()) { 294 #ifdef DEBUG_FILE_SOURCE 295 std::cerr << "FileSource::init: Not a remote URL \"" << m_url.toString() << "\"" << std::endl; 296 #endif 297 // If the file doesn't otherwise have a scheme set (eg. http://, ftp://), set 298 // the scheme to file:// 299 if (m_url.scheme().isEmpty()) m_url.setScheme("file"); 300 301 bool literal = false; 302 m_localFilename = m_url.toLocalFile(); 303 304 // The next code block doesn't work in Qt5, but when everything works 305 // correctly, it is never entered. I decided to leave the loose end 306 // dangling for now. 307 if (m_localFilename == "") { 308 // QUrl may have mishandled the scheme (e.g. in a DOS path) 309 m_localFilename = m_rawFileOrUrl; 310 #ifdef DEBUG_FILE_SOURCE 311 std::cerr << "FileSource::init: Trying literal local filename \"" 312 << m_localFilename << "\"" << std::endl; 313 #endif 314 literal = true; 315 } 316 m_localFilename = QFileInfo(m_localFilename).absoluteFilePath(); 317 318 #ifdef DEBUG_FILE_SOURCE 319 std::cerr << "FileSource::init: URL translates to local filename \"" 320 << m_localFilename << "\" (with literal=" << literal << ")" 321 << std::endl; 322 #endif 323 m_ok = true; 324 m_lastStatus = 200; 325 326 if (!QFileInfo(m_localFilename).exists()) { 327 if (literal) { 328 m_lastStatus = 404; 329 } else { 330 #ifdef DEBUG_FILE_SOURCE 331 std::cerr << "FileSource::init: Local file of this name does not exist, trying URL as a literal filename" << std::endl; 332 #endif 333 // Again, QUrl may have been mistreating us -- 334 // e.g. dropping a part that looks like query data 335 m_localFilename = m_rawFileOrUrl; 336 literal = true; 337 if (!QFileInfo(m_localFilename).exists()) { 338 m_lastStatus = 404; 339 } 340 } 341 } 342 343 m_done = true; 344 return; 345 } 346 347 if (createCacheFile()) { 348 #ifdef DEBUG_FILE_SOURCE 349 std::cerr << "FileSource::init: Already have this one" << std::endl; 350 #endif 351 m_ok = true; 352 if (!QFileInfo(m_localFilename).exists()) { 353 m_lastStatus = 404; 354 } else { 355 m_lastStatus = 200; 356 } 357 m_done = true; 358 return; 359 } 360 361 if (m_localFilename == "") return; 362 363 m_localFile = new QFile(m_localFilename); 364 m_localFile->open(QFile::WriteOnly); 365 366 if (isResource()) { 367 368 // Absent resource file case was dealt with at the top -- this 369 // is the successful case 370 371 QString resourceFileName = m_url.toString(); 372 resourceFileName.replace(QRegularExpression("^qrc:"), ":"); 373 QFile resourceFile(resourceFileName); 374 resourceFile.open(QFile::ReadOnly); 375 QByteArray ba(resourceFile.readAll()); 376 377 #ifdef DEBUG_FILE_SOURCE 378 std::cerr << "Copying " << ba.size() << " bytes from resource file to cache file" << std::endl; 379 #endif 380 381 qint64 written = m_localFile->write(ba); 382 m_localFile->close(); 383 delete m_localFile; 384 m_localFile = nullptr; 385 386 if (written != ba.size()) { 387 #ifdef DEBUG_FILE_SOURCE 388 std::cerr << "Copy failed (wrote " << written << " bytes)" << std::endl; 389 #endif 390 m_ok = false; 391 return; 392 } else { 393 m_ok = true; 394 m_lastStatus = 200; 395 m_done = true; 396 } 397 398 } else { 399 400 QString scheme = m_url.scheme().toLower(); 401 402 #ifdef DEBUG_FILE_SOURCE 403 std::cerr << "FileSource::init: Don't have local copy of \"" 404 << m_url.toString() << "\", retrieving" << std::endl; 405 #endif 406 407 if (scheme == "http" || scheme == "https" || scheme == "ftp") { 408 initRemote(); 409 #ifdef DEBUG_FILE_SOURCE 410 std::cerr << "FileSource: initRemote returned" << std::endl; 411 #endif 412 } else { 413 m_remote = false; 414 m_ok = false; 415 } 416 } 417 418 if (m_ok) { 419 420 QMutexLocker locker(&m_mapMutex); 421 422 if (m_refCountMap[m_url] > 0) { 423 // someone else has been doing the same thing at the same time, 424 // but has got there first 425 cleanup(); 426 m_refCountMap[m_url]++; 427 #ifdef DEBUG_FILE_SOURCE 428 std::cerr << "FileSource::init: Another FileSource has got there first, abandoning our download and using theirs" << std::endl; 429 #endif 430 m_localFilename = m_remoteLocalMap[m_url]; 431 m_refCounted = true; 432 m_ok = true; 433 if (!QFileInfo(m_localFilename).exists()) { 434 m_lastStatus = 404; 435 } 436 m_done = true; 437 return; 438 } 439 440 m_remoteLocalMap[m_url] = m_localFilename; 441 m_refCountMap[m_url]++; 442 m_refCounted = true; 443 444 // if (m_progress && !m_done) { 445 // m_progress->setLabelText 446 // (tr("Downloading %1...").arg(m_url.toString())); 447 // connect(m_progress, SIGNAL(canceled()), this, SLOT(cancelled())); 448 // connect(this, SIGNAL(progress(int)), 449 // m_progress, SLOT(setValue(int))); 450 // } 451 } 452 } 453 454 void 455 FileSource::initRemote() 456 { 457 m_ok = true; 458 459 QNetworkRequest req; 460 req.setUrl(m_url); 461 462 if (m_preferredContentType != "") { 463 #ifdef DEBUG_FILE_SOURCE 464 std::cerr << "FileSource: indicating preferred content type of \"" 465 << m_preferredContentType << "\"" << std::endl; 466 #endif 467 req.setRawHeader 468 ("Accept", 469 QString("%1, */*").arg(m_preferredContentType).toLatin1()); 470 } else { 471 // This is actually the default, however, this call appears to 472 // prevent decompression of .rg files by Qt with some servers that 473 // notice that an .rg file is really a .gz file and respond with 474 // "Content-Encoding: gzip". See the devel mailing list post by Tim 475 // Munro 5/17/2015. 476 req.setRawHeader("Accept-Encoding", "gzip, deflate"); 477 } 478 479 m_reply = nms.localData()->get(req); 480 481 connect(m_reply, &QIODevice::readyRead, 482 this, &FileSource::readyRead); 483 connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)), 484 this, SLOT(replyFailed(QNetworkReply::NetworkError))); 485 connect(m_reply, &QNetworkReply::finished, 486 this, &FileSource::replyFinished); 487 connect(m_reply, &QNetworkReply::metaDataChanged, 488 this, &FileSource::metaDataChanged); 489 connect(m_reply, &QNetworkReply::downloadProgress, 490 this, &FileSource::downloadProgress); 491 } 492 493 void 494 FileSource::cleanup() 495 { 496 if (m_done) { 497 delete m_localFile; // does not actually delete the file 498 m_localFile = nullptr; 499 } 500 m_done = true; 501 if (m_reply) { 502 QNetworkReply *r = m_reply; 503 m_reply = nullptr; 504 505 // Can only call abort() when there are no errors. 506 if (r->error() == QNetworkReply::NoError) { 507 r->abort(); 508 } 509 510 r->deleteLater(); 511 } 512 if (m_localFile) { 513 delete m_localFile; // does not actually delete the file 514 m_localFile = nullptr; 515 } 516 } 517 518 bool 519 FileSource::isRemote(QString fileOrUrl) 520 { 521 // Note that a "scheme" with length 1 is probably a DOS drive letter 522 QString scheme = QUrl(fileOrUrl).scheme().toLower(); 523 if (scheme == "" || scheme == "file" || scheme.length() == 1) return false; 524 return true; 525 } 526 527 bool 528 FileSource::canHandleScheme(QUrl url) 529 { 530 // Note that a "scheme" with length 1 is probably a DOS drive letter 531 QString scheme = url.scheme().toLower(); 532 return (scheme == "http" || scheme == "https" || 533 scheme == "ftp" || scheme == "file" || scheme == "qrc" || 534 scheme == "" || scheme.length() == 1); 535 } 536 537 bool 538 FileSource::isAvailable() 539 { 540 waitForStatus(); 541 542 bool available = true; 543 if (!m_ok) { 544 available = false; 545 } else { 546 // http 2xx status codes mean success 547 available = (m_lastStatus / 100 == 2); 548 } 549 550 #ifdef DEBUG_FILE_SOURCE 551 std::cerr << "FileSource::isAvailable: m_ok: " << m_ok << std::endl; 552 std::cerr << "FileSource::isAvailable: m_lastStatus: " << m_lastStatus << std::endl; 553 std::cerr << "FileSource::isAvailable: " << (available ? "yes" : "no") 554 << std::endl; 555 #endif 556 557 return available; 558 } 559 560 561 // ######################################### 562 // The nested event loops below are horribly fragile. 563 // They can lead to all sorts of bugs due to unexpected reentrancy 564 // (e.g. the user closes the window while in that while loop) 565 // There is no easy solution for synchronous networking operations 566 // in the main thread. Change this API to emit signals and connect to them, instead. 567 // i.e. make it asynchronous, just like KIO or QNetworkReply. 568 // ######################################### 569 570 571 void 572 FileSource::waitForStatus() 573 { 574 // ### BAD, see comment above 575 while (m_ok && (!m_done && m_lastStatus == 0)) { 576 // std::cerr << "waitForStatus: processing (last status " << m_lastStatus << ")" << std::endl; 577 QCoreApplication::processEvents(); 578 } 579 } 580 581 void 582 FileSource::waitForData() 583 { 584 // ### BAD, see comment above 585 while (m_ok && !m_done) { 586 // std::cerr << "FileSource::waitForData: calling QApplication::processEvents" << std::endl; 587 QCoreApplication::processEvents(); 588 usleep(10000); 589 } 590 } 591 592 void 593 FileSource::setLeaveLocalFile(bool leave) 594 { 595 m_leaveLocalFile = leave; 596 } 597 598 bool 599 FileSource::isOK() const 600 { 601 return m_ok; 602 } 603 604 bool 605 FileSource::isDone() const 606 { 607 return m_done; 608 } 609 610 bool 611 FileSource::isResource() const 612 { 613 return m_resource; 614 } 615 616 bool 617 FileSource::isRemote() const 618 { 619 return m_remote; 620 } 621 622 QString 623 FileSource::getLocation() const 624 { 625 return m_url.toString(); 626 } 627 628 QString 629 FileSource::getLocalFilename() const 630 { 631 return m_localFilename; 632 } 633 634 QString 635 FileSource::getBasename() const 636 { 637 return QFileInfo(m_localFilename).fileName(); 638 } 639 640 QString 641 FileSource::getContentType() const 642 { 643 return m_contentType; 644 } 645 646 QString 647 FileSource::getExtension() const 648 { 649 if (m_localFilename != "") { 650 return QFileInfo(m_localFilename).suffix().toLower(); 651 } else { 652 return QFileInfo(m_url.toLocalFile()).suffix().toLower(); 653 } 654 } 655 656 QString 657 FileSource::getErrorString() const 658 { 659 return m_errorString; 660 } 661 662 void 663 FileSource::readyRead() 664 { 665 m_localFile->write(m_reply->readAll()); 666 } 667 668 void 669 FileSource::metaDataChanged() 670 { 671 #ifdef DEBUG_FILE_SOURCE 672 std::cerr << "FileSource::metaDataChanged" << std::endl; 673 #endif 674 675 if (!m_reply) { 676 std::cerr << "WARNING: FileSource::metaDataChanged() called without a reply object being known to us" << std::endl; 677 return; 678 } 679 680 // Handle http transfer status codes. 681 682 int status = 683 m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); 684 685 // If this is a redirection (3xx) code, do the redirect 686 if (status / 100 == 3) { 687 QString location = m_reply->header 688 (QNetworkRequest::LocationHeader).toString(); 689 #ifdef DEBUG_FILE_SOURCE 690 std::cerr << "FileSource::metaDataChanged: redirect to \"" 691 << location << "\" received" << std::endl; 692 #endif 693 if (location != "") { 694 QUrl newUrl(location); 695 if (newUrl != m_url) { 696 cleanup(); 697 deleteCacheFile(); 698 #ifdef DEBUG_FILE_SOURCE 699 decCount(m_url.toString()); 700 incCount(newUrl.toString()); 701 #endif 702 m_url = newUrl; 703 m_localFile = nullptr; 704 m_lastStatus = 0; 705 m_done = false; 706 m_refCounted = false; 707 init(); 708 return; 709 } 710 } 711 } 712 713 m_lastStatus = status; 714 715 // 400 and up are failures, get the error string 716 if (m_lastStatus / 100 >= 4) { 717 m_errorString = QString("%1 %2") 718 .arg(status) 719 .arg(m_reply->attribute 720 (QNetworkRequest::HttpReasonPhraseAttribute).toString()); 721 #ifdef DEBUG_FILE_SOURCE 722 std::cerr << "FileSource::metaDataChanged: " 723 << m_errorString << std::endl; 724 #endif 725 } else { 726 #ifdef DEBUG_FILE_SOURCE 727 std::cerr << "FileSource::metaDataChanged: status: " 728 << m_lastStatus << std::endl; 729 #endif 730 m_contentType = 731 m_reply->header(QNetworkRequest::ContentTypeHeader).toString(); 732 } 733 734 emit statusAvailable(); 735 } 736 737 void 738 FileSource::downloadProgress(qint64 done, qint64 total) 739 { 740 int percent = int((double(done) / double(total)) * 100.0 - 0.1); 741 emit progress(percent); 742 } 743 744 void 745 FileSource::cancelled() 746 { 747 m_done = true; 748 cleanup(); 749 750 m_ok = false; 751 m_errorString = tr("Download cancelled"); 752 } 753 754 void 755 FileSource::replyFinished() 756 { 757 emit progress(100); 758 759 #ifdef DEBUG_FILE_SOURCE 760 std::cerr << "FileSource::replyFinished()" << std::endl; 761 #endif 762 763 if (m_done) return; 764 765 QString scheme = m_url.scheme().toLower(); 766 // For ftp transfers, replyFinished() will be called on success. 767 // metaDataChanged() is never called for ftp transfers. 768 if (scheme == "ftp") 769 m_lastStatus = 200; // http ok 770 771 bool error = (m_lastStatus / 100 >= 4); 772 773 cleanup(); 774 775 if (!error) { 776 QFileInfo fi(m_localFilename); 777 if (!fi.exists()) { 778 m_errorString = tr("Failed to create local file %1").arg(m_localFilename); 779 error = true; 780 } else if (fi.size() == 0) { 781 m_errorString = tr("File contains no data!"); 782 error = true; 783 } 784 } 785 786 if (error) { 787 #ifdef DEBUG_FILE_SOURCE 788 std::cerr << "FileSource::done: error is " << error << ", deleting cache file" << std::endl; 789 #endif 790 deleteCacheFile(); 791 } 792 793 m_ok = !error; 794 if (m_localFile) m_localFile->flush(); 795 m_done = true; 796 emit ready(); 797 } 798 799 void 800 FileSource::replyFailed(QNetworkReply::NetworkError networkError) 801 { 802 #ifdef DEBUG_FILE_SOURCE 803 std::cerr << "FileSource::replyFailed(" << networkError << ")" << std::endl; 804 #else 805 (void)networkError; // Suppress compiler warning 806 #endif 807 808 emit progress(100); 809 if (!m_reply) { 810 std::cerr << "WARNING: FileSource::replyFailed() called without a reply object being known to us" << std::endl; 811 } else { 812 m_errorString = m_reply->errorString(); 813 } 814 m_ok = false; 815 m_done = true; 816 cleanup(); 817 emit ready(); 818 } 819 820 void 821 FileSource::deleteCacheFile() 822 { 823 #ifdef DEBUG_FILE_SOURCE 824 std::cerr << "FileSource::deleteCacheFile(\"" << m_localFilename << "\")" << std::endl; 825 #endif 826 827 cleanup(); 828 829 if (m_localFilename == "") { 830 return; 831 } 832 833 if (!isRemote()) { 834 #ifdef DEBUG_FILE_SOURCE 835 std::cerr << "not a cache file" << std::endl; 836 #endif 837 return; 838 } 839 840 if (m_refCounted) { 841 842 QMutexLocker locker(&m_mapMutex); 843 m_refCounted = false; 844 845 if (m_refCountMap[m_url] > 0) { 846 m_refCountMap[m_url]--; 847 #ifdef DEBUG_FILE_SOURCE 848 std::cerr << "reduced ref count to " << m_refCountMap[m_url] << std::endl; 849 #endif 850 if (m_refCountMap[m_url] > 0) { 851 m_done = true; 852 return; 853 } 854 } 855 } 856 857 m_fileCreationMutex.lock(); 858 859 if (!QFile(m_localFilename).remove()) { 860 #ifdef DEBUG_FILE_SOURCE 861 std::cerr << "FileSource::deleteCacheFile: ERROR: Failed to delete file \"" << m_localFilename << "\"" << std::endl; 862 #endif 863 } else { 864 #ifdef DEBUG_FILE_SOURCE 865 std::cerr << "FileSource::deleteCacheFile: Deleted cache file \"" << m_localFilename << "\"" << std::endl; 866 #endif 867 m_localFilename = ""; 868 } 869 870 m_fileCreationMutex.unlock(); 871 872 m_done = true; 873 } 874 875 bool 876 FileSource::createCacheFile() 877 { 878 { 879 QMutexLocker locker(&m_mapMutex); 880 881 #ifdef DEBUG_FILE_SOURCE 882 std::cerr << "FileSource::createCacheFile: refcount is " << m_refCountMap[m_url] << std::endl; 883 #endif 884 885 if (m_refCountMap[m_url] > 0) { 886 m_refCountMap[m_url]++; 887 m_localFilename = m_remoteLocalMap[m_url]; 888 #ifdef DEBUG_FILE_SOURCE 889 std::cerr << "raised it to " << m_refCountMap[m_url] << std::endl; 890 #endif 891 m_refCounted = true; 892 return true; 893 } 894 } 895 896 QDir dir; 897 try { 898 dir.setPath(TempDirectory::getInstance()->getSubDirectoryPath("download")); 899 } catch (const DirectoryCreationFailed &f) { 900 #ifdef DEBUG_FILE_SOURCE 901 std::cerr << "FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.what() << std::endl; 902 #endif 903 m_localFilename = ""; 904 return false; 905 } 906 907 QString filepart = m_url.path().section('/', -1, -1, 908 QString::SectionSkipEmpty); 909 910 QString extension = ""; 911 if (filepart.contains('.')) extension = filepart.section('.', -1); 912 913 QString base = filepart; 914 if (extension != "") { 915 base = base.left(base.length() - extension.length() - 1); 916 } 917 if (base == "") base = "remote"; 918 919 QString filename; 920 921 if (extension == "") { 922 filename = base; 923 } else { 924 filename = QString("%1.%2").arg(base).arg(extension); 925 } 926 927 QString filepath(dir.filePath(filename)); 928 929 #ifdef DEBUG_FILE_SOURCE 930 std::cerr << "FileSource::createCacheFile: URL is \"" << m_url.toString() << "\", dir is \"" << dir.path() << "\", base \"" << base << "\", extension \"" << extension << "\", filebase \"" << filename << "\", filename \"" << filepath << "\"" << std::endl; 931 #endif 932 933 QMutexLocker fcLocker(&m_fileCreationMutex); 934 935 ++m_count; 936 937 if (QFileInfo(filepath).exists() || 938 !QFile(filepath).open(QFile::WriteOnly)) { 939 940 #ifdef DEBUG_FILE_SOURCE 941 std::cerr << "FileSource::createCacheFile: Failed to create local file \"" 942 << filepath << "\" for URL \"" 943 << m_url.toString() << "\" (or file already exists): appending suffix instead" << std::endl; 944 #endif 945 946 if (extension == "") { 947 filename = QString("%1_%2").arg(base).arg(m_count); 948 } else { 949 filename = QString("%1_%2.%3").arg(base).arg(m_count).arg(extension); 950 } 951 filepath = dir.filePath(filename); 952 953 if (QFileInfo(filepath).exists() || 954 !QFile(filepath).open(QFile::WriteOnly)) { 955 956 #ifdef DEBUG_FILE_SOURCE 957 std::cerr << "FileSource::createCacheFile: ERROR: Failed to create local file \"" 958 << filepath << "\" for URL \"" 959 << m_url.toString() << "\" (or file already exists)" << std::endl; 960 #endif 961 962 m_localFilename = ""; 963 return false; 964 } 965 } 966 967 #ifdef DEBUG_FILE_SOURCE 968 std::cerr << "FileSource::createCacheFile: url " 969 << m_url.toString() << " -> local filename " 970 << filepath << std::endl; 971 #endif 972 973 m_localFilename = filepath; 974 return false; 975 } 976 977 } 978 979 980