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