aboutsummaryrefslogtreecommitdiffstats
path: root/core/imagedownloader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/imagedownloader.cpp')
-rw-r--r--core/imagedownloader.cpp149
1 files changed, 82 insertions, 67 deletions
diff --git a/core/imagedownloader.cpp b/core/imagedownloader.cpp
index b56dd8b04..995bf2e02 100644
--- a/core/imagedownloader.cpp
+++ b/core/imagedownloader.cpp
@@ -21,81 +21,71 @@ static QUrl cloudImageURL(const char *filename)
return QUrl::fromUserInput(QString("https://cloud.subsurface-divelog.org/images/").append(hash));
}
-ImageDownloader::ImageDownloader(const QString &filename_in) : filename(filename_in)
+// Note: this is a global instead of a function-local variable on purpose.
+// We don't want this to be generated in a different thread context if
+// ImageDownloader::instance() is called from a worker thread.
+static ImageDownloader imageDownloader;
+ImageDownloader *ImageDownloader::instance()
{
+ return &imageDownloader;
}
-void ImageDownloader::load(bool fromHash)
+ImageDownloader::ImageDownloader()
{
- if (fromHash && loadFromUrl(cloudImageURL(qPrintable(filename))))
- return;
-
- // If loading from hash failed, try to load from filename
- loadFromUrl(QUrl::fromUserInput(filename));
+ connect(&manager, &QNetworkAccessManager::finished, this, &ImageDownloader::saveImage);
}
-bool ImageDownloader::loadFromUrl(const QUrl &url)
+void ImageDownloader::load(QString filename, bool fromHash)
{
- bool success = false;
- if (url.isValid()) {
- QEventLoop loop;
- QNetworkAccessManager manager;
- QNetworkRequest request(url);
- connect(&manager, &QNetworkAccessManager::finished, this,
- [this,&success] (QNetworkReply *reply) { saveImage(reply, success); });
- connect(&manager, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);
- qDebug() << "Downloading image from" << url;
- QNetworkReply *reply = manager.get(request);
- loop.exec();
- delete reply;
- }
- return success;
+ QUrl url = fromHash ? cloudImageURL(qPrintable(filename)) : QUrl::fromUserInput(filename);
+
+ if (!url.isValid())
+ emit failed(filename);
+
+ QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::User, filename);
+ request.setAttribute(static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User + 1), fromHash);
+ manager.get(request);
}
-void ImageDownloader::saveImage(QNetworkReply *reply, bool &success)
+void ImageDownloader::saveImage(QNetworkReply *reply)
{
- success = false;
- QByteArray imageData = reply->readAll();
- QImage image;
- image.loadFromData(imageData);
- if (image.isNull())
- return;
- success = true;
- QCryptographicHash hash(QCryptographicHash::Sha1);
- hash.addData(imageData);
- QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first();
- QDir dir(path);
- if (!dir.exists())
- dir.mkpath(path);
- QFile imageFile(path.append("/").append(hash.result().toHex()));
- if (imageFile.open(QIODevice::WriteOnly)) {
- qDebug() << "Write image to" << imageFile.fileName();
- QDataStream stream(&imageFile);
- stream.writeRawData(imageData.data(), imageData.length());
- imageFile.waitForBytesWritten(-1);
- imageFile.close();
- learnHash(filename, imageFile.fileName(), hash.result());
+ QString filename = reply->request().attribute(QNetworkRequest::User).toString();
+
+ if (reply->error() != QNetworkReply::NoError) {
+ bool fromHash = reply->request().attribute(static_cast<QNetworkRequest::Attribute>(QNetworkRequest::User + 1)).toBool();
+ if (fromHash)
+ load(filename, false);
+ else
+ emit failed(filename);
+ } else {
+ QByteArray imageData = reply->readAll();
+ QCryptographicHash hash(QCryptographicHash::Sha1);
+ hash.addData(imageData);
+ QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first();
+ QDir dir(path);
+ if (!dir.exists())
+ dir.mkpath(path);
+ QFile imageFile(path.append("/").append(hash.result().toHex()));
+ if (imageFile.open(QIODevice::WriteOnly)) {
+ qDebug() << "Write image to" << imageFile.fileName();
+ QDataStream stream(&imageFile);
+ stream.writeRawData(imageData.data(), imageData.length());
+ imageFile.waitForBytesWritten(-1);
+ imageFile.close();
+ learnHash(filename, imageFile.fileName(), hash.result());
+ }
+ emit loaded(filename);
}
- // This should be called to make the picture actually show.
- // Problem is DivePictureModel is not in core.
- // Nevertheless, the image shows when the dive is selected the next time.
- // DivePictureModel::instance()->updateDivePictures();
+ reply->deleteLater();
}
static void loadPicture(QString filename, bool fromHash)
{
- static QSet<QString> queuedPictures;
- static QMutex pictureQueueMutex;
-
- QMutexLocker locker(&pictureQueueMutex);
- if (queuedPictures.contains(filename))
- return;
- queuedPictures.insert(filename);
- locker.unlock();
-
- ImageDownloader download(filename);
- download.load(fromHash);
+ // This has to be done in UI main thread, because QNetworkManager refuses
+ // to treat requests from other threads.
+ QMetaObject::invokeMethod(ImageDownloader::instance(), "load", Qt::AutoConnection, Q_ARG(QString, filename), Q_ARG(bool, fromHash));
}
// Overwrite QImage::load() so that we can perform better error reporting.
@@ -108,13 +98,15 @@ static QImage loadImage(const QString &fileName, const char *format = nullptr)
return res;
}
-QImage getHashedImage(const QString &file)
+// Returns: thumbnail, still loading
+static std::pair<QImage,bool> getHashedImage(const QString &file)
{
- QImage res;
+ QImage thumb;
+ bool stillLoading = false;
QUrl url = QUrl::fromUserInput(localFilePath(file));
if (url.isLocalFile())
- res = loadImage(url.toLocalFile());
- if (res.isNull()) {
+ thumb = loadImage(url.toLocalFile());
+ if (thumb.isNull()) {
// This did not load anything. Let's try to get the image from other sources
// Let's try to load it locally via its hash
QString filenameLocal = localFilePath(qPrintable(file));
@@ -124,22 +116,24 @@ QImage getHashedImage(const QString &file)
// Try the cloud server
// TODO: This is dead code at the moment.
loadPicture(file, true);
+ stillLoading = true;
} else {
// Load locally from translated file name
- res = loadImage(filenameLocal);
- if (!res.isNull()) {
+ thumb = loadImage(filenameLocal);
+ if (!thumb.isNull()) {
// Make sure the hash still matches the image file
hashPicture(filenameLocal);
} else {
// Interpret filename as URL
loadPicture(filenameLocal, false);
+ stillLoading = true;
}
}
} else {
// We loaded successfully. Now, make sure hash is up to date.
hashPicture(file);
}
- return res;
+ return { thumb, stillLoading };
}
static QImage renderIcon(const char *id, int size)
@@ -157,6 +151,8 @@ Thumbnailer::Thumbnailer() : failImage(renderIcon(":filter-close", maxThumbnailS
// Currently, we only process one image at a time. Stefan Fuchs reported problems when
// calculating multiple thumbnails at once and this hopefully helps.
pool.setMaxThreadCount(1);
+ connect(ImageDownloader::instance(), &ImageDownloader::loaded, this, &Thumbnailer::imageDownloaded);
+ connect(ImageDownloader::instance(), &ImageDownloader::failed, this, &Thumbnailer::imageDownloadFailed);
}
Thumbnailer *Thumbnailer::instance()
@@ -217,7 +213,11 @@ void Thumbnailer::processItem(QString filename)
QImage thumbnail = getThumbnailFromCache(filename);
if (thumbnail.isNull()) {
- thumbnail = getHashedImage(filename);
+ auto res = getHashedImage(filename);
+ if (res.second)
+ return;
+ thumbnail = res.first;
+
if (thumbnail.isNull()) {
thumbnail = failImage;
} else {
@@ -232,6 +232,21 @@ void Thumbnailer::processItem(QString filename)
workingOn.remove(filename);
}
+void Thumbnailer::imageDownloaded(QString filename)
+{
+ // Image was downloaded and the filename connected with a hash.
+ // Try thumbnailing again.
+ QMutexLocker l(&lock);
+ workingOn[filename] = QtConcurrent::run(&pool, [this, filename]() { processItem(filename); });
+}
+
+void Thumbnailer::imageDownloadFailed(QString filename)
+{
+ emit thumbnailChanged(filename, failImage);
+ QMutexLocker l(&lock);
+ workingOn.remove(filename);
+}
+
QImage Thumbnailer::fetchThumbnail(PictureEntry &entry)
{
QMutexLocker l(&lock);