Merge lp:~aacid/thumbnailer/asyncprovider into lp:thumbnailer

Proposed by Albert Astals Cid
Status: Rejected
Rejected by: Albert Astals Cid
Proposed branch: lp:~aacid/thumbnailer/asyncprovider
Merge into: lp:thumbnailer
Diff against target: 722 lines (+342/-167)
13 files modified
include/thumbnailer.h (+2/-0)
plugins/Ubuntu/Thumbnailer/albumartgenerator.cpp (+86/-40)
plugins/Ubuntu/Thumbnailer/albumartgenerator.h (+7/-5)
plugins/Ubuntu/Thumbnailer/artgeneratorcommon.cpp (+1/-2)
plugins/Ubuntu/Thumbnailer/artgeneratorcommon.h (+1/-1)
plugins/Ubuntu/Thumbnailer/artistartgenerator.cpp (+87/-40)
plugins/Ubuntu/Thumbnailer/artistartgenerator.h (+8/-3)
plugins/Ubuntu/Thumbnailer/plugin.cpp (+7/-3)
plugins/Ubuntu/Thumbnailer/plugin.h (+3/-0)
plugins/Ubuntu/Thumbnailer/thumbnailgenerator.cpp (+110/-66)
plugins/Ubuntu/Thumbnailer/thumbnailgenerator.h (+10/-7)
src/libthumbnailer.map (+1/-0)
src/thumbnailer.cpp (+19/-0)
To merge this branch: bzr merge lp:~aacid/thumbnailer/asyncprovider
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Unity Team Pending
Review via email: mp+253188@code.launchpad.net

Description of the change

Implement an Async image provider

This is a skeleton needs work and also unreleased https://codereview.qt-project.org/#/c/108540/ for now

Decisions that need to be taken:
 * How to organize the thread pools, i'm using 2 thread pools with two threads each, one for slow and one for fast jobs. But one can organize them as wanted

Questions that need answering:
 * Why the artist/album providers use dbus but not the thumbnailer one?

Work that needs to be done:
 * Add a dbus query to see if art/artist is cached (or don't use the dbus api for those two either) to make them use the fast/slow pool acoordingly
 * Make the dbus based providers cancellable when waiting for dbus

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:124
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~aacid/thumbnailer/asyncprovider/+merge/253188/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/thumbnailer-ci/142/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/thumbnailer-vivid-amd64-ci/36/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/thumbnailer-vivid-armhf-ci/34/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/thumbnailer-vivid-i386-ci/31/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/thumbnailer-ci/142/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Jussi Pakkanen (jpakkane) wrote :

> Why the artist/album providers use dbus but not the thumbnailer one?

Because artist and album art deal with public data that is downloaded from the net. Thus they are cached in one global location.

Thumbnails are app-specific and for security reasons are stored in an app-specific directory. Otherwise apps could snoop on each others' thumbnails which would be a security violation.

Because of this the former uses dbus and the latter hits the file system directly.

Revision history for this message
Albert Astals Cid (aacid) wrote :

Setting as WiP for the moment until upstream Qt change is landed

Unmerged revisions

125. By Albert Astals Cid

New API

124. By Albert Astals Cid

Implement an Async image provider

This is a skeleton needs work and also needs an unreleased Qt for now

Just so people can comment on it

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/thumbnailer.h'
2--- include/thumbnailer.h 2014-08-01 13:22:43 +0000
3+++ include/thumbnailer.h 2015-03-27 14:56:52 +0000
4@@ -76,6 +76,8 @@
5 std::string get_artist_art(const std::string &artist, const std::string &album,
6 ThumbnailSize desiredSize, ThumbnailPolicy policy);
7
8+ bool has_cached_thumbnail(const std::string &filename, ThumbnailSize desired_size) const;
9+
10 private:
11 Thumbnailer(const Thumbnailer &t);
12 Thumbnailer & operator=(const Thumbnailer &t);
13
14=== modified file 'plugins/Ubuntu/Thumbnailer/albumartgenerator.cpp'
15--- plugins/Ubuntu/Thumbnailer/albumartgenerator.cpp 2015-03-05 08:47:32 +0000
16+++ plugins/Ubuntu/Thumbnailer/albumartgenerator.cpp 2015-03-27 14:56:52 +0000
17@@ -25,6 +25,7 @@
18 #include <QUrlQuery>
19 #include <QDBusUnixFileDescriptor>
20 #include <QDBusReply>
21+#include <QThreadPool>
22
23 static const char DEFAULT_ALBUM_ART[] = "/usr/share/thumbnailer/icons/album_missing.png";
24
25@@ -33,52 +34,97 @@
26 static const char THUMBNAILER_IFACE[] = "com.canonical.Thumbnailer";
27 static const char GET_ALBUM_ART[] = "GetAlbumArt";
28
29-AlbumArtGenerator::AlbumArtGenerator()
30- : QQuickImageProvider(QQuickImageProvider::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading)
31+AlbumArtGenerator::AlbumArtGenerator(QThreadPool *fastPool, QThreadPool *slowPool)
32+ : m_fastPool(fastPool)
33+ , m_slowPool(slowPool)
34 {
35 }
36
37-static QImage fallbackImage(QSize *realSize) {
38+static QImage fallbackImage() {
39 QImage fallback;
40 fallback.load(DEFAULT_ALBUM_ART);
41- *realSize = fallback.size();
42 return fallback;
43 }
44
45-QImage AlbumArtGenerator::requestImage(const QString &id, QSize *realSize,
46- const QSize &requestedSize) {
47- QUrlQuery query(id);
48- if (!query.hasQueryItem("artist") || !query.hasQueryItem("album")) {
49- qWarning() << "Invalid albumart uri:" << id;
50- return fallbackImage(realSize);
51- }
52-
53- if (!connection) {
54- // Create them here and not them on the constrcutor so they belong to the proper thread
55- connection.reset(new QDBusConnection(QDBusConnection::connectToBus(QDBusConnection::SessionBus, "album_art_generator_dbus_connection")));
56- iface.reset(new QDBusInterface(BUS_NAME, BUS_PATH, THUMBNAILER_IFACE, *connection));
57- }
58-
59- const QString artist = query.queryItemValue("artist", QUrl::FullyDecoded);
60- const QString album = query.queryItemValue("album", QUrl::FullyDecoded);
61-
62- QString desiredSize = sizeToDesiredSizeString(requestedSize);
63-
64- // perform dbus call
65- QDBusReply<QDBusUnixFileDescriptor> reply = iface->call(
66- GET_ALBUM_ART, artist, album, desiredSize);
67- if (!reply.isValid()) {
68- qWarning() << "D-Bus error: " << reply.error().message();
69- return fallbackImage(realSize);
70- }
71-
72- try {
73- return imageFromFd(reply.value().fileDescriptor(), realSize);
74- } catch (const std::exception &e) {
75- qDebug() << "Album art loader failed: " << e.what();
76- } catch (...) {
77- qDebug() << "Unknown error when generating image.";
78- }
79-
80- return fallbackImage(realSize);
81+class AlbumArtGeneratorImageResponse : public QQuickImageResponse, public QRunnable
82+{
83+public:
84+ AlbumArtGeneratorImageResponse(const QString &id, const QSize &requestedSize)
85+ : m_id(id), m_requestedSize(requestedSize), m_texture(nullptr), m_cancelled(false)
86+ {
87+ setAutoDelete(false);
88+ }
89+
90+ QQuickTextureFactory *textureFactory() const override
91+ {
92+ return m_texture;
93+ }
94+
95+ void run() override
96+ {
97+ if (m_cancelled) {
98+ qDebug() << "RUNNING CANCELLED ALBUM ART" << m_id;
99+ emit finished();
100+ return;
101+ }
102+
103+ qDebug() << "RUNNING ALBUM ART" << m_id;
104+
105+ QImage image;
106+ const QUrlQuery query(m_id);
107+ if (query.hasQueryItem("artist") && query.hasQueryItem("album")) {
108+ QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SessionBus, "album_art_generator_dbus_connection");
109+ QDBusInterface iface(BUS_NAME, BUS_PATH, THUMBNAILER_IFACE, connection);
110+
111+ const QString artist = query.queryItemValue("artist", QUrl::FullyDecoded);
112+ const QString album = query.queryItemValue("album", QUrl::FullyDecoded);
113+ const QString desiredSize = sizeToDesiredSizeString(m_requestedSize);
114+
115+ // perform dbus call
116+ // TODO make this async so we can actually cancel at this point too
117+ QDBusReply<QDBusUnixFileDescriptor> reply = iface.call(
118+ GET_ALBUM_ART, artist, album, desiredSize);
119+ if (reply.isValid()) {
120+ try {
121+ image = imageFromFd(reply.value().fileDescriptor());
122+ } catch (const std::exception &e) {
123+ qDebug() << "Album art loader failed: " << e.what();
124+ } catch (...) {
125+ qDebug() << "Unknown error when generating image.";
126+ }
127+ } else {
128+ qWarning() << "D-Bus error: " << reply.error().message();
129+ }
130+ } else {
131+ qWarning() << "Invalid albumart uri:" << m_id;
132+ }
133+
134+ if (image.isNull())
135+ image = fallbackImage();
136+
137+ m_texture = QQuickTextureFactory::textureFactoryForImage(image);
138+ emit finished();
139+ }
140+
141+ void cancel() override
142+ {
143+ m_cancelled = true;
144+ qDebug() << "CANCEL ALBUM ART" << m_id;
145+ }
146+
147+private:
148+ QString m_id;
149+ QSize m_requestedSize;
150+ QQuickTextureFactory *m_texture;
151+ bool m_cancelled;
152+};
153+
154+QQuickImageResponse *AlbumArtGenerator::requestImageResponse(const QString &id, const QSize &requestedSize)
155+{
156+ qDebug() << "ALBUM ART" << id;
157+ AlbumArtGeneratorImageResponse *response = new AlbumArtGeneratorImageResponse(id, requestedSize);
158+ // TODO we need a dbus call in the thumbnailer to know if to queue on the fast or in the slow one
159+ // TODO Why are we using a thumbnailer object in thumbnail generator but a dbus service here?
160+ m_fastPool->start(response);
161+ return response;
162 }
163
164=== modified file 'plugins/Ubuntu/Thumbnailer/albumartgenerator.h'
165--- plugins/Ubuntu/Thumbnailer/albumartgenerator.h 2015-03-05 08:43:59 +0000
166+++ plugins/Ubuntu/Thumbnailer/albumartgenerator.h 2015-03-27 14:56:52 +0000
167@@ -25,15 +25,17 @@
168
169 #include <memory>
170
171-class AlbumArtGenerator: public QQuickImageProvider
172+class QThreadPool;
173+
174+class AlbumArtGenerator: public QQuickAsyncImageProvider
175 {
176 private:
177- std::unique_ptr<QDBusConnection> connection;
178- std::unique_ptr<QDBusInterface> iface;
179+ QThreadPool *m_fastPool;
180+ QThreadPool *m_slowPool;
181
182 public:
183- AlbumArtGenerator();
184- QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
185+ AlbumArtGenerator(QThreadPool *fastPool, QThreadPool *slowPool);
186+ QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
187 };
188
189 #endif
190
191=== modified file 'plugins/Ubuntu/Thumbnailer/artgeneratorcommon.cpp'
192--- plugins/Ubuntu/Thumbnailer/artgeneratorcommon.cpp 2014-08-01 11:02:12 +0000
193+++ plugins/Ubuntu/Thumbnailer/artgeneratorcommon.cpp 2015-03-27 14:56:52 +0000
194@@ -35,12 +35,11 @@
195 return desiredSize;
196 }
197
198-QImage imageFromFd(int fd, QSize *realSize)
199+QImage imageFromFd(int fd)
200 {
201 QFile file;
202 file.open(fd, QIODevice::ReadOnly);
203 QImage image;
204 image.load(&file, NULL);
205- *realSize = image.size();
206 return image;
207 }
208
209=== modified file 'plugins/Ubuntu/Thumbnailer/artgeneratorcommon.h'
210--- plugins/Ubuntu/Thumbnailer/artgeneratorcommon.h 2014-08-01 11:02:12 +0000
211+++ plugins/Ubuntu/Thumbnailer/artgeneratorcommon.h 2015-03-27 14:56:52 +0000
212@@ -23,6 +23,6 @@
213 #include <QImage>
214
215 QString sizeToDesiredSizeString(const QSize& requestedSize);
216-QImage imageFromFd(int fd, QSize *realSize);
217+QImage imageFromFd(int fd);
218
219 #endif
220
221=== modified file 'plugins/Ubuntu/Thumbnailer/artistartgenerator.cpp'
222--- plugins/Ubuntu/Thumbnailer/artistartgenerator.cpp 2015-03-05 08:47:32 +0000
223+++ plugins/Ubuntu/Thumbnailer/artistartgenerator.cpp 2015-03-27 14:56:52 +0000
224@@ -26,6 +26,7 @@
225 #include <QUrlQuery>
226 #include <QDBusUnixFileDescriptor>
227 #include <QDBusReply>
228+#include <QThreadPool>
229
230 static const char DEFAULT_ARTIST_ART[] = "/usr/share/thumbnailer/icons/album_missing.png";
231
232@@ -34,52 +35,98 @@
233 static const char THUMBNAILER_IFACE[] = "com.canonical.Thumbnailer";
234 static const char GET_ARTIST_ART[] = "GetArtistArt";
235
236-ArtistArtGenerator::ArtistArtGenerator()
237- : QQuickImageProvider(QQuickImageProvider::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading)
238+ArtistArtGenerator::ArtistArtGenerator(QThreadPool *fastPool, QThreadPool *slowPool)
239+ : m_fastPool(fastPool)
240+ , m_slowPool(slowPool)
241 {
242 }
243
244-static QImage fallbackImage(QSize *realSize) {
245+static QImage fallbackImage() {
246 QImage fallback;
247 fallback.load(DEFAULT_ARTIST_ART);
248- *realSize = fallback.size();
249 return fallback;
250 }
251
252-QImage ArtistArtGenerator::requestImage(const QString &id, QSize *realSize,
253- const QSize &requestedSize) {
254- QUrlQuery query(id);
255- if (!query.hasQueryItem("artist") || !query.hasQueryItem("album")) {
256- qWarning() << "Invalid artistart uri:" << id;
257- return fallbackImage(realSize);
258- }
259-
260- if (!connection) {
261- // Create them here and not them on the constrcutor so they belong to the proper thread
262- connection.reset(new QDBusConnection(QDBusConnection::connectToBus(QDBusConnection::SessionBus, "artist_art_generator_dbus_connection")));
263- iface.reset(new QDBusInterface(BUS_NAME, BUS_PATH, THUMBNAILER_IFACE, *connection));
264- }
265-
266- const QString artist = query.queryItemValue("artist", QUrl::FullyDecoded);
267- const QString album = query.queryItemValue("album", QUrl::FullyDecoded);
268-
269- QString desiredSize = sizeToDesiredSizeString(requestedSize);
270-
271- // perform dbus call
272- QDBusReply<QDBusUnixFileDescriptor> reply = iface->call(
273- GET_ARTIST_ART, artist, album, desiredSize);
274- if (!reply.isValid()) {
275- qWarning() << "D-Bus error: " << reply.error().message();
276- return fallbackImage(realSize);
277- }
278-
279- try {
280- return imageFromFd(reply.value().fileDescriptor(), realSize);
281- } catch (const std::exception &e) {
282- qDebug() << "Artist art loader failed: " << e.what();
283- } catch (...) {
284- qDebug() << "Unknown error when generating image.";
285- }
286-
287- return fallbackImage(realSize);
288+class ArtistArtGeneratorImageResponse : public QQuickImageResponse, public QRunnable
289+{
290+public:
291+ ArtistArtGeneratorImageResponse(const QString &id, const QSize &requestedSize)
292+ : m_id(id), m_requestedSize(requestedSize), m_texture(nullptr), m_cancelled(false)
293+ {
294+ setAutoDelete(false);
295+ }
296+
297+ QQuickTextureFactory *textureFactory() const override
298+ {
299+ return m_texture;
300+ }
301+
302+ void run() override
303+ {
304+ if (m_cancelled) {
305+ qDebug() << "RUNNING CANCELLED ARTIST ART" << m_id;
306+ emit finished();
307+ return;
308+ }
309+
310+ qDebug() << "RUNNING ARTIST ART" << m_id;
311+ QImage image;
312+ const QUrlQuery query(m_id);
313+ if (query.hasQueryItem("artist") && query.hasQueryItem("album")) {
314+ QDBusConnection connection = QDBusConnection::connectToBus(QDBusConnection::SessionBus, "artist_art_generator_dbus_connection");
315+ QDBusInterface iface(BUS_NAME, BUS_PATH, THUMBNAILER_IFACE, connection);
316+
317+ const QString artist = query.queryItemValue("artist", QUrl::FullyDecoded);
318+ const QString album = query.queryItemValue("album", QUrl::FullyDecoded);
319+ const QString desiredSize = sizeToDesiredSizeString(m_requestedSize);
320+
321+ // perform dbus call
322+ // TODO make this async so we can actually cancel at this point too
323+ QDBusReply<QDBusUnixFileDescriptor> reply = iface.call(
324+ GET_ARTIST_ART, artist, album, desiredSize);
325+ if (reply.isValid()) {
326+ try {
327+ image = imageFromFd(reply.value().fileDescriptor());
328+ } catch (const std::exception &e) {
329+ qDebug() << "Artist art loader failed: " << e.what();
330+ } catch (...) {
331+ qDebug() << "Unknown error when generating image.";
332+ }
333+ } else {
334+ qWarning() << "D-Bus error: " << reply.error().message();
335+ }
336+ } else {
337+ qWarning() << "Invalid artistart uri:" << m_id;
338+ }
339+
340+ if (image.isNull())
341+ image = fallbackImage();
342+
343+ m_texture = QQuickTextureFactory::textureFactoryForImage(image);
344+ qDebug() << "DONE RUNNING ARTIST ART" << m_id;
345+ emit finished();
346+ }
347+
348+ void cancel() override
349+ {
350+ m_cancelled = true;
351+ qDebug() << "CANCEL ARTIST ART" << m_id;
352+ }
353+
354+private:
355+ QString m_id;
356+ QSize m_requestedSize;
357+ QQuickTextureFactory *m_texture;
358+ bool m_cancelled;
359+};
360+
361+QQuickImageResponse *ArtistArtGenerator::requestImageResponse(const QString &id, const QSize &requestedSize)
362+{
363+ qDebug() << "REQUEST ARTIST ART" << id;
364+ ArtistArtGeneratorImageResponse *response = new ArtistArtGeneratorImageResponse(id, requestedSize);
365+ // TODO we need a dbus call in the thumbnailer to know if to queue on the fast or in the slow one
366+ // TODO Why are we using a thumbnailer object in thumbnail generator but a dbus service here?
367+ m_fastPool->start(response);
368+ return response;
369 }
370+
371
372=== modified file 'plugins/Ubuntu/Thumbnailer/artistartgenerator.h'
373--- plugins/Ubuntu/Thumbnailer/artistartgenerator.h 2015-03-05 08:43:59 +0000
374+++ plugins/Ubuntu/Thumbnailer/artistartgenerator.h 2015-03-27 14:56:52 +0000
375@@ -24,15 +24,20 @@
376
377 #include <memory>
378
379-class ArtistArtGenerator: public QQuickImageProvider
380+class QThreadPool;
381+
382+class ArtistArtGenerator: public QQuickAsyncImageProvider
383 {
384 private:
385 std::unique_ptr<QDBusConnection> connection;
386 std::unique_ptr<QDBusInterface> iface;
387
388+ QThreadPool *m_fastPool;
389+ QThreadPool *m_slowPool;
390+
391 public:
392- ArtistArtGenerator();
393- QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
394+ ArtistArtGenerator(QThreadPool *fastPool, QThreadPool *slowPool);
395+ QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
396 };
397
398 #endif
399
400=== modified file 'plugins/Ubuntu/Thumbnailer/plugin.cpp'
401--- plugins/Ubuntu/Thumbnailer/plugin.cpp 2014-08-01 10:21:31 +0000
402+++ plugins/Ubuntu/Thumbnailer/plugin.cpp 2015-03-27 14:56:52 +0000
403@@ -30,8 +30,12 @@
404 void ThumbnailerPlugin::initializeEngine(QQmlEngine *engine, const char *uri) {
405 QQmlExtensionPlugin::initializeEngine(engine, uri);
406
407+ // TODO decide if this is the threading strategy we want
408+ fastPool.setMaxThreadCount(2);
409+ slowPool.setMaxThreadCount(2);
410+
411 try {
412- engine->addImageProvider("albumart", new AlbumArtGenerator());
413+ engine->addImageProvider("albumart", new AlbumArtGenerator(&fastPool, &slowPool));
414 } catch (const std::exception &e) {
415 qWarning() << "Failed to register albumart image provider:" << e.what();
416 } catch (...) {
417@@ -39,7 +43,7 @@
418 }
419
420 try {
421- engine->addImageProvider("artistart", new ArtistArtGenerator());
422+ engine->addImageProvider("artistart", new ArtistArtGenerator(&fastPool, &slowPool));
423 } catch (const std::exception &e) {
424 qWarning() << "Failed to register artistart image provider:" << e.what();
425 } catch (...) {
426@@ -47,7 +51,7 @@
427 }
428
429 try {
430- engine->addImageProvider("thumbnailer", new ThumbnailGenerator());
431+ engine->addImageProvider("thumbnailer", new ThumbnailGenerator(&fastPool, &slowPool));
432 } catch (const std::exception &e) {
433 qWarning() << "Failed to register thumbnailer image provider:" << e.what();
434 } catch (...) {
435
436=== modified file 'plugins/Ubuntu/Thumbnailer/plugin.h'
437--- plugins/Ubuntu/Thumbnailer/plugin.h 2014-03-28 10:11:44 +0000
438+++ plugins/Ubuntu/Thumbnailer/plugin.h 2015-03-27 14:56:52 +0000
439@@ -29,6 +29,9 @@
440 public:
441 virtual void registerTypes(const char *uri) override;
442 virtual void initializeEngine(QQmlEngine *engine, const char *uri) override;
443+
444+ QThreadPool fastPool;
445+ QThreadPool slowPool;
446 };
447
448 #endif
449
450=== modified file 'plugins/Ubuntu/Thumbnailer/thumbnailgenerator.cpp'
451--- plugins/Ubuntu/Thumbnailer/thumbnailgenerator.cpp 2015-01-12 17:11:04 +0000
452+++ plugins/Ubuntu/Thumbnailer/thumbnailgenerator.cpp 2015-03-27 14:56:52 +0000
453@@ -22,75 +22,12 @@
454 #include <QMimeDatabase>
455 #include <QUrl>
456 #include <QImageReader>
457+#include <QThreadPool>
458
459 static const char *DEFAULT_VIDEO_ART = "/usr/share/thumbnailer/icons/video_missing.png";
460 static const char *DEFAULT_ALBUM_ART = "/usr/share/thumbnailer/icons/album_missing.png";
461
462-ThumbnailGenerator::ThumbnailGenerator() : QQuickImageProvider(QQuickImageProvider::Image,
463- QQmlImageProviderBase::ForceAsynchronousImageLoading) {
464-
465-}
466-
467-QImage ThumbnailGenerator::requestImage(const QString &id, QSize *realSize,
468- const QSize &requestedSize) {
469- /* Allow appending a query string (e.g. ?something=timestamp)
470- * to the id and then ignore it.
471- * This is workaround to force reloading a thumbnail when it has
472- * the same file name on disk but we know the content has changed.
473- * It is necessary because in such a situation the QML image cache
474- * will kick in and this ImageProvider will never get called.
475- * The only "solution" is setting Image.cache = false, but in some
476- * cases we don't want to do that for performance reasons, so this
477- * is the only way around the issue for now. */
478- std::string src_path(QUrl(id).path().toUtf8().data());
479- std::string tgt_path;
480- try {
481- ThumbnailSize desiredSize;
482- const int xlarge_cutoff = 512;
483- const int large_cutoff = 256;
484- const int small_cutoff = 128;
485- if(requestedSize.width() > xlarge_cutoff || requestedSize.height() > xlarge_cutoff) {
486- desiredSize = TN_SIZE_ORIGINAL;
487- } else if(requestedSize.width() > large_cutoff || requestedSize.height() > large_cutoff) {
488- desiredSize = TN_SIZE_XLARGE;
489- } else if(requestedSize.width() > small_cutoff || requestedSize.height() > small_cutoff) {
490- desiredSize = TN_SIZE_LARGE;
491- } else {
492- desiredSize = TN_SIZE_SMALL;
493- }
494- tgt_path = tn.get_thumbnail(src_path, desiredSize);
495- if(!tgt_path.empty()) {
496- QString tgt(tgt_path.c_str());
497- QImage image;
498- QImageReader reader;
499- reader.setFileName(tgt);
500- QSize imageSize = reader.size();
501- if (!requestedSize.isNull() &&
502- (imageSize.width() > requestedSize.width() ||
503- imageSize.height() > requestedSize.height())) {
504- QSize validRequestedSize = requestedSize;
505- if (validRequestedSize.width() == 0) {
506- validRequestedSize.setWidth(imageSize.width());
507- }
508- if (validRequestedSize.height() == 0) {
509- validRequestedSize.setHeight(imageSize.height());
510- }
511- imageSize.scale(validRequestedSize, Qt::KeepAspectRatio);
512- reader.setScaledSize(imageSize);
513- }
514- image = reader.read();
515- *realSize = image.size();
516- return image;
517- }
518- } catch(std::runtime_error &e) {
519- qDebug() << "Thumbnail generator failed: " << e.what();
520- }
521- return getFallbackImage(id, realSize, requestedSize);
522-}
523-
524-QImage ThumbnailGenerator::getFallbackImage(const QString &id, QSize *size,
525- const QSize &requestedSize) {
526- Q_UNUSED(requestedSize);
527+static QImage getFallbackImage(const QString &id) {
528 QMimeDatabase db;
529 QMimeType mime = db.mimeTypeForFile(id);
530 QImage result;
531@@ -99,6 +36,113 @@
532 } else if(mime.name().contains("video")) {
533 result.load(DEFAULT_VIDEO_ART);
534 }
535- *size = result.size();
536 return result;
537 }
538+
539+static ThumbnailSize thumbnailSizeFromQSize(const QSize &requestedSize) {
540+ const int xlarge_cutoff = 512;
541+ const int large_cutoff = 256;
542+ const int small_cutoff = 128;
543+
544+ if(requestedSize.width() > xlarge_cutoff || requestedSize.height() > xlarge_cutoff) {
545+ return TN_SIZE_ORIGINAL;
546+ } else if(requestedSize.width() > large_cutoff || requestedSize.height() > large_cutoff) {
547+ return TN_SIZE_XLARGE;
548+ } else if(requestedSize.width() > small_cutoff || requestedSize.height() > small_cutoff) {
549+ return TN_SIZE_LARGE;
550+ } else {
551+ return TN_SIZE_SMALL;
552+ }
553+}
554+
555+class ThumbnailGeneratorImageResponse : public QQuickImageResponse, public QRunnable
556+{
557+public:
558+ ThumbnailGeneratorImageResponse(const QString &id, const QSize &requestedSize, Thumbnailer *tn)
559+ : m_id(id), m_requestedSize(requestedSize), m_tn(tn), m_texture(nullptr), m_cancelled(false)
560+ {
561+ setAutoDelete(false);
562+ }
563+
564+ QQuickTextureFactory *textureFactory() const
565+ {
566+ return m_texture;
567+ }
568+
569+ void run()
570+ {
571+ if (m_cancelled) {
572+ emit finished();
573+ return;
574+ }
575+
576+ /* Allow appending a query string (e.g. ?something=timestamp)
577+ * to the id and then ignore it.
578+ * This is workaround to force reloading a thumbnail when it has
579+ * the same file name on disk but we know the content has changed.
580+ * It is necessary because in such a situation the QML image cache
581+ * will kick in and this ImageProvider will never get called.
582+ * The only "solution" is setting Image.cache = false, but in some
583+ * cases we don't want to do that for performance reasons, so this
584+ * is the only way around the issue for now. */
585+ QImage image;
586+ try {
587+ const std::string src_path(QUrl(m_id).path().toUtf8().data());
588+ const std::string tgt_path = m_tn->get_thumbnail(src_path, thumbnailSizeFromQSize(m_requestedSize));
589+ if(!tgt_path.empty()) {
590+ QString tgt(tgt_path.c_str());
591+ QImageReader reader;
592+ reader.setFileName(tgt);
593+ QSize imageSize = reader.size();
594+ if (!m_requestedSize.isNull() &&
595+ (imageSize.width() > m_requestedSize.width() ||
596+ imageSize.height() > m_requestedSize.height())) {
597+ QSize validRequestedSize = m_requestedSize;
598+ if (validRequestedSize.width() == 0) {
599+ validRequestedSize.setWidth(imageSize.width());
600+ }
601+ if (validRequestedSize.height() == 0) {
602+ validRequestedSize.setHeight(imageSize.height());
603+ }
604+ imageSize.scale(validRequestedSize, Qt::KeepAspectRatio);
605+ reader.setScaledSize(imageSize);
606+ }
607+ image = reader.read();
608+ }
609+ } catch(std::runtime_error &e) {
610+ qDebug() << "Thumbnail generator failed: " << e.what();
611+ }
612+ if (image.isNull()) {
613+ image = getFallbackImage(m_id);
614+ }
615+
616+ m_texture = QQuickTextureFactory::textureFactoryForImage(image);
617+ emit finished();
618+ }
619+
620+private:
621+ QString m_id;
622+ QSize m_requestedSize;
623+ Thumbnailer *m_tn;
624+ QQuickTextureFactory *m_texture;
625+ bool m_cancelled;
626+};
627+
628+
629+ThumbnailGenerator::ThumbnailGenerator(QThreadPool *fastPool, QThreadPool *slowPool)
630+ : m_fastPool(fastPool)
631+ , m_slowPool(slowPool)
632+{
633+}
634+
635+QQuickImageResponse *ThumbnailGenerator::requestImageResponse(const QString &id, const QSize &requestedSize) {
636+ qDebug() << "THUMBNAIL" << id;
637+ ThumbnailGeneratorImageResponse *response = new ThumbnailGeneratorImageResponse(id, requestedSize, &m_thumbnailer);
638+ const std::string src_path(QUrl(id).path().toUtf8().data());
639+ if (m_thumbnailer.has_cached_thumbnail(src_path, thumbnailSizeFromQSize(requestedSize))) {
640+ m_fastPool->start(response);
641+ } else {
642+ m_slowPool->start(response);
643+ }
644+ return response;
645+}
646
647=== modified file 'plugins/Ubuntu/Thumbnailer/thumbnailgenerator.h'
648--- plugins/Ubuntu/Thumbnailer/thumbnailgenerator.h 2014-09-15 08:13:32 +0000
649+++ plugins/Ubuntu/Thumbnailer/thumbnailgenerator.h 2015-03-27 14:56:52 +0000
650@@ -22,20 +22,23 @@
651 #include <QQuickImageProvider>
652 #include <thumbnailer.h>
653
654-class ThumbnailGenerator: public QQuickImageProvider
655+class QThreadPool;
656+
657+class ThumbnailGenerator: public QQuickAsyncImageProvider
658 {
659-private:
660- Thumbnailer tn;
661-
662 public:
663- ThumbnailGenerator();
664+ ThumbnailGenerator(QThreadPool *fastPool, QThreadPool *slowPool);
665 ThumbnailGenerator(const ThumbnailGenerator &other) = delete;
666 const ThumbnailGenerator & operator=(const ThumbnailGenerator &other) = delete;
667 ThumbnailGenerator(ThumbnailGenerator &&other) = delete;
668 const ThumbnailGenerator & operator=(ThumbnailGenerator &&other) = delete;
669
670- QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
671- QImage getFallbackImage(const QString &id, QSize *size, const QSize &requestedSize);
672+ QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
673+
674+private:
675+ Thumbnailer m_thumbnailer;
676+ QThreadPool *m_fastPool;
677+ QThreadPool *m_slowPool;
678 };
679
680 #endif
681
682=== modified file 'src/libthumbnailer.map'
683--- src/libthumbnailer.map 2014-08-01 13:22:43 +0000
684+++ src/libthumbnailer.map 2015-03-27 14:56:52 +0000
685@@ -7,6 +7,7 @@
686 "Thumbnailer::get_thumbnail(std::string const&, ThumbnailSize, ThumbnailPolicy)";
687 "Thumbnailer::get_album_art(std::string const&, std::string const&, ThumbnailSize, ThumbnailPolicy)";
688 "Thumbnailer::get_artist_art(std::string const&, std::string const&, ThumbnailSize, ThumbnailPolicy)";
689+ "Thumbnailer::has_cached_thumbnail(std::string const&, ThumbnailSize) const";
690 };
691 local:
692 extern "C++" {
693
694=== modified file 'src/thumbnailer.cpp'
695--- src/thumbnailer.cpp 2015-01-20 15:29:10 +0000
696+++ src/thumbnailer.cpp 2015-03-27 14:56:52 +0000
697@@ -212,6 +212,25 @@
698 Thumbnailer::~Thumbnailer() {
699 delete p;
700 }
701+
702+bool Thumbnailer::has_cached_thumbnail(const std::string &filename, ThumbnailSize desired_size) const
703+{
704+ string abspath;
705+ if(filename.empty()) {
706+ return "";
707+ }
708+ if(filename[0] != '/') {
709+ auto cwd = getcwd(nullptr, 0);
710+ abspath += cwd;
711+ free(cwd);
712+ abspath += "/" + filename;
713+ } else {
714+ abspath = filename;
715+ }
716+ std::string estimate = p->cache.get_if_exists(abspath, desired_size);
717+ return !estimate.empty();
718+}
719+
720 std::string Thumbnailer::get_thumbnail(const std::string &filename, ThumbnailSize desired_size,
721 ThumbnailPolicy policy) {
722 string abspath;

Subscribers

People subscribed via source and target branches

to all changes: