Merge lp:~aacid/thumbnailer/asyncprovider into lp:thumbnailer
- asyncprovider
- Merge into trunk
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 |
Related bugs: |
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 |
Commit message
Description of the change
Implement an Async image provider
This is a skeleton needs work and also unreleased https:/
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
PS Jenkins bot (ps-jenkins) wrote : | # |
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.
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
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; |
FAILED: Continuous integration, rev:124 /code.launchpad .net/~aacid/ thumbnailer/ asyncprovider/ +merge/ 253188/ +edit-commit- message
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:/
http:// jenkins. qa.ubuntu. com/job/ thumbnailer- ci/142/ jenkins. qa.ubuntu. com/job/ thumbnailer- vivid-amd64- ci/36/console jenkins. qa.ubuntu. com/job/ thumbnailer- vivid-armhf- ci/34/console jenkins. qa.ubuntu. com/job/ thumbnailer- vivid-i386- ci/31/console
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/thumbnailer -ci/142/ rebuild
http://