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