diff options
author | Berthold Stoeger <bstoeger@mail.tuwien.ac.at> | 2018-03-11 12:58:55 +0100 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2018-05-13 13:52:35 -0700 |
commit | d33e3b22fc5d2e878ee6e03a25e99870484da217 (patch) | |
tree | af13233a98f4091187c405221b0a5b8216c69589 /core/imagedownloader.cpp | |
parent | 19fa2966efae211f210ef0a761040d8bf2b7edfb (diff) | |
download | subsurface-d33e3b22fc5d2e878ee6e03a25e99870484da217.tar.gz |
Dive pictures: refactor image downloader
Instead of generating one ImageDownloader object per image to be
downloaded and running every image download in a separate worker
thread, use one global ImageDownloader object owned by the UI thread.
The images are downloaded using event based IO (as probably was the
intention of the QNetworkManager class).
User-visible change: after download from the internet, the thumbnail
is shown without having to change dives.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Diffstat (limited to 'core/imagedownloader.cpp')
-rw-r--r-- | core/imagedownloader.cpp | 149 |
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); |