23 #include <QNetworkAccessManager> 24 #include <QNetworkReply> 27 #include <QCoreApplication> 28 #include <QThreadStorage> 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);
58 if (urlExtantCountMap.find(url) == urlExtantCountMap.end()) {
59 urlExtantCountMap[url] = 1;
61 ++urlExtantCountMap[url];
63 cerr <<
"FileSource: Now " << urlExtantCountMap[url] <<
" for this url, " << extantCount <<
" total" << endl;
65 static void decCount(QString url) {
66 QMutexLocker locker(&countMutex);
68 --urlExtantCountMap[url];
69 cerr <<
"FileSource: Now " << urlExtantCountMap[url] <<
" for this url, " << extantCount <<
" total" << endl;
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;
82 cerr <<
"FileSource::debugReport done\n" << endl;
88 static QThreadStorage<QNetworkAccessManager *>
nms;
91 QString preferredContentType) :
93 m_url(fileOrUrl, QUrl::StrictMode),
108 m_url = QUrl(
"qrc" + fileOrUrl);
111 if (
m_url.toString() ==
"") {
112 m_url = QUrl(fileOrUrl, QUrl::TolerantMode);
115 #ifdef DEBUG_FILE_SOURCE 116 cerr <<
"FileSource::FileSource(" << fileOrUrl <<
"): url <" <<
m_url.toString() <<
">" << endl;
117 incCount(
m_url.toString());
121 cerr <<
"FileSource::FileSource: ERROR: Unsupported scheme in URL \"" <<
m_url.toString() <<
"\"" << endl;
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;
133 m_url = QUrl::fromEncoded(fileOrUrl.toLatin1());
134 #ifdef DEBUG_FILE_SOURCE 135 cerr <<
"FileSource::FileSource: URL is now \"" <<
m_url.toString() <<
"\"" << endl;
141 (fileOrUrl.contains(
'%') ||
142 fileOrUrl.contains(
"--"))) {
151 cerr <<
"FileSource::FileSource: Failed to retrieve URL \"" 153 <<
"\" as human-readable URL; " 154 <<
"trying again treating it as encoded URL" 164 m_url = QUrl::fromEncoded(fileOrUrl.toLatin1());
178 #ifdef DEBUG_FILE_SOURCE 179 cerr <<
"FileSource::FileSource(string) exiting" << endl;
197 #ifdef DEBUG_FILE_SOURCE 198 cerr <<
"FileSource::FileSource(" << url.toString() <<
") [as url]" << endl;
199 incCount(
m_url.toString());
203 cerr <<
"FileSource::FileSource: ERROR: Unsupported scheme in URL \"" <<
m_url.toString() <<
"\"" << endl;
210 #ifdef DEBUG_FILE_SOURCE 211 cerr <<
"FileSource::FileSource(url) exiting" << endl;
230 #ifdef DEBUG_FILE_SOURCE 231 cerr <<
"FileSource::FileSource(" <<
m_url.toString() <<
") [copy ctor]" << endl;
232 incCount(
m_url.toString());
236 cerr <<
"FileSource::FileSource: ERROR: Unsupported scheme in URL \"" <<
m_url.toString() <<
"\"" << endl;
245 #ifdef DEBUG_FILE_SOURCE 246 cerr <<
"FileSource::FileSource(copy ctor): ref count is " 251 #ifdef DEBUG_FILE_SOURCE 264 #ifdef DEBUG_FILE_SOURCE 265 cerr <<
"FileSource::FileSource(" <<
m_url.toString() <<
") [copy ctor]: note: local filename is \"" <<
m_localFilename <<
"\"" << endl;
268 #ifdef DEBUG_FILE_SOURCE 269 cerr <<
"FileSource::FileSource(copy ctor) exiting" << endl;
275 #ifdef DEBUG_FILE_SOURCE 276 cerr <<
"FileSource(" <<
m_url.toString() <<
")::~FileSource" << endl;
277 decCount(
m_url.toString());
289 #ifdef DEBUG_FILE_SOURCE 290 cerr <<
"FileSource::init: Is a resource" << endl;
292 QString resourceFile =
m_url.toString();
293 resourceFile.replace(QRegExp(
"^qrc:"),
":");
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;
299 m_url = resourceFile;
305 #ifdef DEBUG_FILE_SOURCE 306 cerr <<
"FileSource::init: Not a remote URL" << endl;
308 bool literal =
false;
314 #ifdef DEBUG_FILE_SOURCE 315 cerr <<
"FileSource::init: Trying literal local filename \"" 322 #ifdef DEBUG_FILE_SOURCE 323 cerr <<
"FileSource::init: URL translates to absolute filename \"" 334 #ifdef DEBUG_FILE_SOURCE 335 cerr <<
"FileSource::init: Local file of this name does not exist, trying URL as a literal filename" << endl;
352 #ifdef DEBUG_FILE_SOURCE 353 cerr <<
"FileSource::init: Already have this one" << endl;
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());
381 #ifdef DEBUG_FILE_SOURCE 382 cerr <<
"Copying " << ba.size() <<
" bytes from resource file to cache file" << endl;
390 if (written != ba.size()) {
391 #ifdef DEBUG_FILE_SOURCE 392 cerr <<
"Copy failed (wrote " << written <<
" bytes)" << endl;
404 QString scheme =
m_url.scheme().toLower();
406 #ifdef DEBUG_FILE_SOURCE 407 cerr <<
"FileSource::init: Don't have local copy of \"" 408 <<
m_url.toString() <<
"\", retrieving" << endl;
411 if (scheme ==
"http" || scheme ==
"https" || scheme ==
"ftp") {
413 #ifdef DEBUG_FILE_SOURCE 414 cerr <<
"FileSource: initRemote returned" << endl;
431 #ifdef DEBUG_FILE_SOURCE 432 cerr <<
"FileSource::init: Another FileSource has got there first, abandoning our download and using theirs" << endl;
450 (tr(
"Downloading %1...").arg(
m_url.toString()));
452 connect(
this, SIGNAL(
progress(
int)),
467 #ifdef DEBUG_FILE_SOURCE 468 cerr <<
"FileSource: indicating preferred content type of \"" 478 if (!nms.hasLocalData()) {
479 #ifdef DEBUG_FILE_SOURCE 482 nms.setLocalData(
new QNetworkAccessManager());
486 m_reply = nms.localData()->get(req);
490 connect(
m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
491 this, SLOT(
replyFailed(QNetworkReply::NetworkError)));
492 connect(
m_reply, SIGNAL(finished()),
510 disconnect(r,
nullptr,
this,
nullptr);
513 if (r->error() == QNetworkReply::NoError) {
528 QString scheme = QUrl(fileOrUrl).scheme().toLower();
529 if (scheme ==
"" || scheme ==
"file" || scheme.length() == 1)
return false;
537 QString scheme = url.scheme().toLower();
538 return (scheme ==
"http" || scheme ==
"https" ||
539 scheme ==
"ftp" || scheme ==
"file" || scheme ==
"qrc" ||
540 scheme ==
"" || scheme.length() == 1);
547 bool available =
true;
554 #ifdef DEBUG_FILE_SOURCE 555 cerr <<
"FileSource::isAvailable: " << (available ?
"yes" :
"no") << endl;
565 QCoreApplication::processEvents();
574 QCoreApplication::processEvents();
618 return m_url.toString();
645 return QFileInfo(
m_url.toLocalFile()).suffix().toLower();
664 #ifdef DEBUG_FILE_SOURCE 665 cerr <<
"FileSource::metaDataChanged" << endl;
669 cerr <<
"WARNING: FileSource::metaDataChanged() called without a reply object being known to us" << endl;
676 m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
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;
686 if (location !=
"") {
687 QUrl newUrl(location);
688 if (newUrl !=
m_url) {
691 #ifdef DEBUG_FILE_SOURCE 692 decCount(
m_url.toString());
693 incCount(newUrl.toString());
713 (QNetworkRequest::HttpReasonPhraseAttribute).toString());
714 #ifdef DEBUG_FILE_SOURCE 715 cerr <<
"FileSource::metaDataChanged: " 719 #ifdef DEBUG_FILE_SOURCE 720 cerr <<
"FileSource::metaDataChanged: " 724 m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
732 int percent = int((
double(done) /
double(total)) * 100.0 - 0.1);
752 #ifdef DEBUG_FILE_SOURCE 753 cerr <<
"FileSource::replyFinished()" << endl;
758 QString scheme =
m_url.scheme().toLower();
761 if (scheme ==
"ftp") {
774 }
else if (fi.size() == 0) {
781 #ifdef DEBUG_FILE_SOURCE 782 cerr <<
"FileSource::done: error is " << error <<
", deleting cache file" << endl;
798 cerr <<
"WARNING: FileSource::replyFailed() called without a reply object being known to us" << endl;
811 #ifdef DEBUG_FILE_SOURCE 812 cerr <<
"FileSource::deleteCacheFile(\"" <<
m_localFilename <<
"\")" << endl;
822 #ifdef DEBUG_FILE_SOURCE 823 cerr <<
"not a cache file" << endl;
835 #ifdef DEBUG_FILE_SOURCE 848 #ifdef DEBUG_FILE_SOURCE 849 cerr <<
"FileSource::deleteCacheFile: ERROR: Failed to delete file \"" <<
m_localFilename <<
"\"" << endl;
852 #ifdef DEBUG_FILE_SOURCE 853 cerr <<
"FileSource::deleteCacheFile: Deleted cache file \"" <<
m_localFilename <<
"\"" << endl;
869 #ifdef DEBUG_FILE_SOURCE 876 #ifdef DEBUG_FILE_SOURCE 887 getSubDirectoryPath(
"download"));
889 #ifdef DEBUG_FILE_SOURCE 890 cerr <<
"FileSource::createCacheFile: ERROR: Failed to create temporary directory: " << f.
what() << endl;
895 QString filepart =
m_url.path().section(
'/', -1, -1,
896 QString::SectionSkipEmpty);
898 QString extension =
"";
899 if (filepart.contains(
'.')) extension = filepart.section(
'.', -1);
901 QString base = filepart;
902 if (extension !=
"") {
903 base = base.left(base.length() - extension.length() - 1);
905 if (base ==
"") base =
"remote";
909 if (extension ==
"") {
912 filename = QString(
"%1.%2").arg(base).arg(extension);
915 QString filepath(dir.filePath(filename));
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;
925 if (QFileInfo(filepath).exists() ||
926 !QFile(filepath).open(QFile::WriteOnly)) {
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;
934 if (extension ==
"") {
935 filename = QString(
"%1_%2").arg(base).arg(
m_count);
937 filename = QString(
"%1_%2.%3").arg(base).arg(
m_count).arg(extension);
939 filepath = dir.filePath(filename);
941 if (QFileInfo(filepath).exists() ||
942 !QFile(filepath).open(QFile::WriteOnly)) {
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;
954 #ifdef DEBUG_FILE_SOURCE 955 cerr <<
"FileSource::createCacheFile: url " 956 <<
m_url.toString() <<
" -> local filename "
static QThreadStorage< QNetworkAccessManager * > nms
void setLeaveLocalFile(bool leave)
Specify whether any local, cached file should remain on disc after this FileSource has been destroyed...
QString getLocation() const
Return the location filename or URL as passed to the constructor.
void downloadProgress(qint64 done, qint64 total)
bool isRemote() const
Return true if this FileSource is referring to a remote URL.
FileSource(QString fileOrUrl, ProgressReporter *reporter=0, QString preferredContentType="")
Construct a FileSource using the given local file path or URL.
QString getLocalFilename() const
Return the name of the local file this FileSource refers to.
void statusAvailable()
Emitted when the file's existence has been tested and/or response header received.
static QMutex m_fileCreationMutex
QString m_preferredContentType
QString getBasename() const
Return the base name, i.e.
static RemoteLocalMap m_remoteLocalMap
void waitForData()
Block on a sub-event-loop until the whole of the data has been retrieved (if it is remote)...
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.
void waitForStatus()
Block on a sub-event-loop until the availability of the file or remote URL is known.
static void debugReport()
Print some stats, if FileSource was compiled with debugging.
static TempDirectory * getInstance()
bool wasCancelled() const
Return true if the operation was cancelled by the user through the ProgressReporter interface...
std::map< QUrl, QString > RemoteLocalMap
void replyFailed(QNetworkReply::NetworkError)
bool isDone() const
Return true if the entire file has been retrieved and is available.
ProgressReporter * m_reporter
FileSource is a class used to refer to the contents of a file that may be either local or at a remote...
std::map< QUrl, int > RemoteRefCountMap
QString getContentType() const
Return the MIME content type of this file, if known.
bool isResource() const
Return true if this FileSource is referring to a QRC resource.
static RemoteRefCountMap m_refCountMap
void ready()
Emitted when the entire file data has been retrieved and the local file is complete (if no error has ...
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...
virtual void setMessage(QString text)=0
QString getExtension() const
Return the file extension for this file, if any.
const char * what() const override
bool isOK() const
Return true if the FileSource object is valid and neither error nor cancellation occurred while retri...
bool isAvailable()
Return true if the file or remote URL exists.