Merge lp:~fboucault/thumbnailer/new_qml_api into lp:thumbnailer
- new_qml_api
- Merge into trunk
Status: | Rejected |
---|---|
Rejected by: | Michi Henning |
Proposed branch: | lp:~fboucault/thumbnailer/new_qml_api |
Merge into: | lp:thumbnailer |
Diff against target: |
777 lines (+631/-14) 13 files modified
debian/libthumbnailer0.symbols (+1/-0) include/thumbnailer.h (+9/-0) plugins/Ubuntu/Thumbnailer/CMakeLists.txt (+2/-0) plugins/Ubuntu/Thumbnailer/plugin.cpp (+3/-0) plugins/Ubuntu/Thumbnailer/qthumbnailer.cpp (+260/-0) plugins/Ubuntu/Thumbnailer/qthumbnailer.h (+114/-0) plugins/Ubuntu/Thumbnailer/thumbnailqueue.cpp (+45/-0) plugins/Ubuntu/Thumbnailer/thumbnailqueue.h (+41/-0) src/libthumbnailer.map (+1/-0) src/thumbnailer.cpp (+33/-12) tests/basic.cpp (+23/-0) tests/qml/tst_image_provider.qml (+2/-2) tests/qml/tst_thumbnailer.qml (+97/-0) |
To merge this branch: | bzr merge lp:~fboucault/thumbnailer/new_qml_api |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michi Henning (community) | Disapprove | ||
Florian Boucault (community) | Needs Fixing | ||
Jussi Pakkanen (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Xavi Garcia (community) | Needs Fixing | ||
Review via email: mp+250832@code.launchpad.net |
Commit message
New QML Thumbnailer API allowing custom thumbnailing schedule.
Videos are queued thumbnailed independently from pictures resulting in a better user experience.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
- 138. By Florian Boucault
-
added unit test
- 139. By Florian Boucault
-
Clearer test
- 140. By Florian Boucault
-
Added missing symbol
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:140
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 141. By Florian Boucault
-
Added QML tests
- 142. By Florian Boucault
-
Added more QML tests
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:142
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Jussi Pakkanen (jpakkane) wrote : | # |
I didn't get to review the functional bits quite yet, but here are some simpler issues in the mean time.
- the qml tests don't seem to be run
- the bit that converts relative file names to absolute ones is duplicated in get_thumbnail and needs_generation, please move it to a shared function in an anonymous namespace
- the name needs_generation seems a bit backwards, would it be better to call it is_cached instead
Florian Boucault (fboucault) wrote : | # |
Qml tests not being run automatically is not a new issue. Should be fixed
separately.
Le 25 févr. 2015 07:52, "Jussi Pakkanen" <email address hidden> a
écrit :
> Review: Needs Fixing
>
> I didn't get to review the functional bits quite yet, but here are some
> simpler issues in the mean time.
>
> - the qml tests don't seem to be run
> - the bit that converts relative file names to absolute ones is duplicated
> in get_thumbnail and needs_generation, please move it to a shared function
> in an anonymous namespace
> - the name needs_generation seems a bit backwards, would it be better to
> call it is_cached instead
>
> --
> https:/
> You are the owner of lp:~fboucault/thumbnailer/new_qml_api.
>
Xavi Garcia (xavi-garcia-mena) wrote : | # |
Other minor changes:
- The new added symbol:
+ (c++)"Thumbnail
The version should be 0replaceme instead of 1.1, so the debian machinery sets the right value.
- In the newly created file qthumbnailer.h members should follow the same rules. Either to have the m_/s_ prefix or none. The first class have prefixes while the second doesn't.
- 143. By Florian Boucault
-
Use proper generic version for new symbol
- 144. By Florian Boucault
-
ThumbnailTask to follow the m_/s_ syntax convention used in other classes.
- 145. By Florian Boucault
-
Renamed thumbnail_
needs_generatio n into thumbnail_is_cached
Florian Boucault (fboucault) wrote : | # |
> Other minor changes:
>
> - The new added symbol:
> + (c++)"Thumbnail
> std::char_
> 1.1
>
> The version should be 0replaceme instead of 1.1, so the debian machinery sets
> the right value.
>
Fixed.
> - In the newly created file qthumbnailer.h members should follow the same
> rules. Either to have the m_/s_ prefix or none. The first class have prefixes
> while the second doesn't.
Fixed.
- 146. By Florian Boucault
-
Factorised common code between thumbnail_is_cached and get_thumbnail into a private function.
Florian Boucault (fboucault) wrote : | # |
> I didn't get to review the functional bits quite yet, but here are some
> simpler issues in the mean time.
>
> - the qml tests don't seem to be run
> - the bit that converts relative file names to absolute ones is duplicated in
> get_thumbnail and needs_generation, please move it to a shared function in an
> anonymous namespace
Fixed.
> - the name needs_generation seems a bit backwards, would it be better to call
> it is_cached instead
Fixed.
- 147. By Florian Boucault
-
Removed manual example
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:147
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Albert Astals Cid (aacid) wrote : | # |
To me this seems a fix on the wrong level, that is, it'll mean people have to use Thumbnail {} instead of Image {}, ideally we could still use Image {} and have parallization at the Qt level, though obviously that means convincing the Qt people that this makes sense and getting it accepted upstream.
Michał Sawicz (saviq) wrote : | # |
Right, +1 to the above. Can you mark this as being a workaround for QTBUG-37988 please.
Florian Boucault (fboucault) wrote : | # |
> To me this seems a fix on the wrong level, that is, it'll mean people have to
> use Thumbnail {} instead of Image {}, ideally we could still use Image {} and
> have parallization at the Qt level, though obviously that means convincing the
> Qt people that this makes sense and getting it accepted upstream.
No matter how much we patch Qt it will never do the right thing, ie. when a thumbnail is being generated, loading thumbnails that have already been generated previously should not be blocked.
Jussi Pakkanen (jpakkane) wrote : | # |
Let me try to condense Florian's concerns for those who have not had to deal with the process of thumbnailing.
Creating a video screenshot thumbnail takes several seconds (up to 5) on the device.
Creating a thumbnail for other types (mostly images) takes a fraction of a second. Loading a preexisting thumbnail takes roughly the same.
Suppose you have n threads for thumbnailing and want to display n videos and m images. If you submit the videos first, the images are stuck until the first video gets processed.
What we want is to be able to submit both images and videos and have the system prioritise those thumbnails that are quick to load (like, say, reserving one thread for them and one for video screenshots).
Qt's image provider does not have priorisation capabilities and to fix our use case something like that needs to be added to Qt. This is the Correct thing to do, but on the other hand it is a lot more work than just increasing the amount of threads for QQuickImageProv
Florian Boucault (fboucault) wrote : | # |
> Let me try to condense Florian's concerns for those who have not had to deal
> with the process of thumbnailing.
>
> Creating a video screenshot thumbnail takes several seconds (up to 5) on the
> device.
>
> Creating a thumbnail for other types (mostly images) takes a fraction of a
> second. Loading a preexisting thumbnail takes roughly the same.
>
> Suppose you have n threads for thumbnailing and want to display n videos and m
> images. If you submit the videos first, the images are stuck until the first
> video gets processed.
>
> What we want is to be able to submit both images and videos and have the
> system prioritise those thumbnails that are quick to load (like, say,
> reserving one thread for them and one for video screenshots).
>
> Qt's image provider does not have priorisation capabilities and to fix our use
> case something like that needs to be added to Qt. This is the Correct thing to
> do, but on the other hand it is a lot more work than just increasing the
> amount of threads for QQuickImageProv
Yes and no. I am also fixing a case that you merged with another one as being the same:
case 1) image with non existing thumbnail
case 2) video with non existing thumbnail
case 3) any media with already existing thumbnail
1) is fast but not instant
2) is slow
3) is instant
But because of the queuing, if a video is being thumbnailed, 1) and 3) become slow.
Adding N more threads to the image provider infrastructure would fix the issue of 1) becoming slow but 3) would still not be instant in some cases.
Jussi Pakkanen (jpakkane) wrote : | # |
LGTM. One thing to note is that there are two queues called video and image. The image queue deals with other image types besides images, such as audio files (and possibly in the future also pdf files, libreoffice docs etc). Thus it might make sense to rename it s_otherQueue, s_basicQueue or something like that. This is totally up to you, though, and not a requirement.
Florian Boucault (fboucault) wrote : | # |
Will be reworked in 1-2 weeks to make use of the new Qt QQuickImageProvider async API that aacid is working on. That will allow us to not have that new Thmubnailer {} QML API.
Michi Henning (michihenning) wrote : | # |
Rejecting this, seeing it is obsolete.
Unmerged revisions
- 147. By Florian Boucault
-
Removed manual example
- 146. By Florian Boucault
-
Factorised common code between thumbnail_is_cached and get_thumbnail into a private function.
- 145. By Florian Boucault
-
Renamed thumbnail_
needs_generatio n into thumbnail_is_cached - 144. By Florian Boucault
-
ThumbnailTask to follow the m_/s_ syntax convention used in other classes.
- 143. By Florian Boucault
-
Use proper generic version for new symbol
- 142. By Florian Boucault
-
Added more QML tests
- 141. By Florian Boucault
-
Added QML tests
- 140. By Florian Boucault
-
Added missing symbol
- 139. By Florian Boucault
-
Clearer test
- 138. By Florian Boucault
-
added unit test
Preview Diff
1 | === modified file 'debian/libthumbnailer0.symbols' |
2 | --- debian/libthumbnailer0.symbols 2014-08-11 14:20:31 +0000 |
3 | +++ debian/libthumbnailer0.symbols 2015-02-25 18:25:10 +0000 |
4 | @@ -5,3 +5,4 @@ |
5 | (c++)"Thumbnailer::Thumbnailer()@Base" 1.0 |
6 | (c++)"Thumbnailer::~Thumbnailer()@Base" 1.0 |
7 | (c++)"Thumbnailer::get_artist_art(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, ThumbnailSize, ThumbnailPolicy)@Base" 1.1+14.10.20140811 |
8 | + (c++)"Thumbnailer::thumbnail_is_cached(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, ThumbnailSize)@Base" 0replaceme |
9 | |
10 | === modified file 'include/thumbnailer.h' |
11 | --- include/thumbnailer.h 2014-08-01 13:22:43 +0000 |
12 | +++ include/thumbnailer.h 2015-02-25 18:25:10 +0000 |
13 | @@ -76,6 +76,15 @@ |
14 | std::string get_artist_art(const std::string &artist, const std::string &album, |
15 | ThumbnailSize desiredSize, ThumbnailPolicy policy); |
16 | |
17 | + /** |
18 | + * Returns whether or not the thumbnail was previously generated and is readily |
19 | + * available. |
20 | + * |
21 | + * If true the next call to get_thumbnail would lead to generating the |
22 | + * thumbnail and could potentially take a long time. |
23 | + */ |
24 | + bool thumbnail_is_cached(const std::string &filename, ThumbnailSize desired_size); |
25 | + |
26 | private: |
27 | Thumbnailer(const Thumbnailer &t); |
28 | Thumbnailer & operator=(const Thumbnailer &t); |
29 | |
30 | === modified file 'plugins/Ubuntu/Thumbnailer/CMakeLists.txt' |
31 | --- plugins/Ubuntu/Thumbnailer/CMakeLists.txt 2014-08-01 11:02:12 +0000 |
32 | +++ plugins/Ubuntu/Thumbnailer/CMakeLists.txt 2015-02-25 18:25:10 +0000 |
33 | @@ -7,6 +7,8 @@ |
34 | artgeneratorcommon.cpp |
35 | artistartgenerator.cpp |
36 | thumbnailgenerator.cpp |
37 | + qthumbnailer.cpp |
38 | + thumbnailqueue.cpp |
39 | ) |
40 | |
41 | set_target_properties(thumbnailer-qml PROPERTIES AUTOMOC TRUE) |
42 | |
43 | === modified file 'plugins/Ubuntu/Thumbnailer/plugin.cpp' |
44 | --- plugins/Ubuntu/Thumbnailer/plugin.cpp 2014-08-01 10:21:31 +0000 |
45 | +++ plugins/Ubuntu/Thumbnailer/plugin.cpp 2015-02-25 18:25:10 +0000 |
46 | @@ -20,11 +20,14 @@ |
47 | #include "albumartgenerator.h" |
48 | #include "artistartgenerator.h" |
49 | #include "thumbnailgenerator.h" |
50 | +#include "qthumbnailer.h" |
51 | |
52 | void ThumbnailerPlugin::registerTypes(const char *uri) { |
53 | qmlRegisterTypeNotAvailable( |
54 | uri, 0, 1, "__ThumbnailerIgnoreMe", |
55 | "Ignore this: QML plugins must contain at least one type"); |
56 | + qmlRegisterType<QThumbnailer>(uri, 0, 1, "Thumbnailer"); |
57 | + |
58 | } |
59 | |
60 | void ThumbnailerPlugin::initializeEngine(QQmlEngine *engine, const char *uri) { |
61 | |
62 | === added file 'plugins/Ubuntu/Thumbnailer/qthumbnailer.cpp' |
63 | --- plugins/Ubuntu/Thumbnailer/qthumbnailer.cpp 1970-01-01 00:00:00 +0000 |
64 | +++ plugins/Ubuntu/Thumbnailer/qthumbnailer.cpp 2015-02-25 18:25:10 +0000 |
65 | @@ -0,0 +1,260 @@ |
66 | +/* |
67 | + * Copyright 2015 Canonical Ltd. |
68 | + * |
69 | + * This program is free software; you can redistribute it and/or modify |
70 | + * it under the terms of the GNU Lesser General Public License as published by |
71 | + * the Free Software Foundation; version 3. |
72 | + * |
73 | + * This program is distributed in the hope that it will be useful, |
74 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
75 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
76 | + * GNU Lesser General Public License for more details. |
77 | + * |
78 | + * You should have received a copy of the GNU Lesser General Public License |
79 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
80 | + * |
81 | + * Authors: Florian Boucault <florian.boucault@canonical.com> |
82 | +*/ |
83 | + |
84 | +#include "qthumbnailer.h" |
85 | +#include <stdexcept> |
86 | +#include <QtCore/QDebug> |
87 | + |
88 | +/*! |
89 | + \qmltype Thumbnailer |
90 | + \instantiates QThumbnailer |
91 | + \inqmlmodule Ubuntu.Thumbnailer 0.1 |
92 | + \ingroup ubuntu |
93 | + \brief Thumbnailer provides a way to load thumbnails of any media (image, video, etc.) |
94 | + |
95 | + When an image representation of a media, e.g. a video, is needed a thumbnail can be |
96 | + generated and retrieved by Thumbnailer. Once generated a thumbnail is cached on |
97 | + disk and reused when needed. |
98 | + |
99 | + Thumbnails generated have a size always greater or equal to \l size. |
100 | + |
101 | + In the following example a thumbnail of a media located at 'pathToMediaFile' is |
102 | + generated and then loaded by a standard QML Image object. Its size is set so that |
103 | + it matches exactly the size required by the Image as to minimize the computation |
104 | + and memory used while still looking as good as possible. |
105 | + |
106 | + \qml |
107 | + import QtQuick 2.0 |
108 | + import Ubuntu.Thumbnailer 0.1 |
109 | + |
110 | + Item { |
111 | + Thumbnailer { |
112 | + id: thumbnailer |
113 | + source: pathToMediaFile |
114 | + size: image.sourceSize |
115 | + } |
116 | + |
117 | + Image { |
118 | + id: image |
119 | + |
120 | + fillMode: Image.PreserveAspectFit |
121 | + asynchronous: true |
122 | + source: thumbnailer.thumbnail |
123 | + sourceSize { |
124 | + width: image.width |
125 | + height: image.height |
126 | + } |
127 | + } |
128 | + } |
129 | + \endqml |
130 | + |
131 | +*/ |
132 | + |
133 | +QThumbnailer::QThumbnailer(QObject* parent) : |
134 | + QObject(parent), |
135 | + m_componentCompleted(false), |
136 | + m_source(""), |
137 | + m_thumbnail(""), |
138 | + m_thumbnailsize(TN_SIZE_SMALL), |
139 | + m_currentTask(NULL) |
140 | +{ |
141 | +} |
142 | + |
143 | +QThumbnailer::~QThumbnailer() |
144 | +{ |
145 | + cancelUpdateThumbnail(); |
146 | +} |
147 | + |
148 | +void QThumbnailer::classBegin() |
149 | +{ |
150 | +} |
151 | + |
152 | +void QThumbnailer::componentComplete() |
153 | +{ |
154 | + m_componentCompleted = true; |
155 | + updateThumbnail(); |
156 | +} |
157 | + |
158 | +/*! |
159 | + \qmlproperty url Thumbnailer::source |
160 | + |
161 | + URL of the media to be thumbnailed. Once set \l thumbnail will eventually |
162 | + hold the URL of the thumbnail. |
163 | + */ |
164 | +QUrl QThumbnailer::source() const |
165 | +{ |
166 | + return m_source; |
167 | +} |
168 | + |
169 | +void QThumbnailer::setSource(QUrl source) |
170 | +{ |
171 | + if (source != m_source) { |
172 | + m_source = source; |
173 | + Q_EMIT sourceChanged(); |
174 | + updateThumbnail(); |
175 | + } |
176 | +} |
177 | + |
178 | +/*! |
179 | + \qmlproperty size Thumbnailer::size |
180 | + |
181 | + Size requested for the thumbnail. The resulting thumbnail's size will be |
182 | + at least \l size. |
183 | + |
184 | + Warning: the thumbnail's size can be much greater than \l size. |
185 | + */ |
186 | +QSize QThumbnailer::size() const |
187 | +{ |
188 | + return m_size; |
189 | +} |
190 | + |
191 | +ThumbnailSize thumbnailSizeFromSize(QSize size) |
192 | +{ |
193 | + const int xlarge_cutoff = 512; |
194 | + const int large_cutoff = 256; |
195 | + const int small_cutoff = 128; |
196 | + if(size.width() > xlarge_cutoff || size.height() > xlarge_cutoff) { |
197 | + return TN_SIZE_ORIGINAL; |
198 | + } else if(size.width() > large_cutoff || size.height() > large_cutoff) { |
199 | + return TN_SIZE_XLARGE; |
200 | + } else if(size.width() > small_cutoff || size.height() > small_cutoff) { |
201 | + return TN_SIZE_LARGE; |
202 | + } else { |
203 | + return TN_SIZE_SMALL; |
204 | + } |
205 | +} |
206 | + |
207 | +void QThumbnailer::setSize(QSize size) |
208 | +{ |
209 | + if (size != m_size) { |
210 | + m_size = size; |
211 | + m_thumbnailsize = thumbnailSizeFromSize(m_size); |
212 | + Q_EMIT sizeChanged(); |
213 | + updateThumbnail(); |
214 | + } |
215 | +} |
216 | + |
217 | +/*! |
218 | + \qmlproperty url Thumbnailer::thumbnail |
219 | + |
220 | + URL of the thumbnail generated once \l source and \l size are set. |
221 | + */ |
222 | +QUrl QThumbnailer::thumbnail() const |
223 | +{ |
224 | + return m_thumbnail; |
225 | +} |
226 | + |
227 | +void QThumbnailer::setThumbnail(QString thumbnail) |
228 | +{ |
229 | + QUrl thumbnailUrl; |
230 | + if (thumbnail.isEmpty()) { |
231 | + thumbnailUrl = QUrl(); |
232 | + } else { |
233 | + thumbnailUrl = QUrl::fromLocalFile(thumbnail); |
234 | + } |
235 | + if (thumbnailUrl != m_thumbnail) { |
236 | + m_thumbnail = thumbnailUrl; |
237 | + Q_EMIT thumbnailChanged(); |
238 | + } |
239 | +} |
240 | + |
241 | +void QThumbnailer::updateThumbnail() |
242 | +{ |
243 | + if (!m_componentCompleted) { |
244 | + return; |
245 | + } |
246 | + |
247 | + cancelUpdateThumbnail(); |
248 | + |
249 | + if (m_size.isEmpty() || m_source.isEmpty()) { |
250 | + setThumbnail(""); |
251 | + return; |
252 | + } |
253 | + |
254 | + bool slowUpdate = s_thumbnailer.thumbnail_is_cached(m_source.path().toStdString(), |
255 | + m_thumbnailsize); |
256 | + if (!slowUpdate) { |
257 | + // if we know retrieving the thumbnail is fast because it is readily |
258 | + // available on disk, set it immediately |
259 | + QString thumbnail = thumbnailPathForMedia(m_source.path(), m_thumbnailsize); |
260 | + setThumbnail(thumbnail); |
261 | + } else { |
262 | + // otherwise enqueue the thumbnailing task that will then get processed |
263 | + // in a background thread and will end up setting the thumbnail URL |
264 | + ThumbnailTask* task = new ThumbnailTask; |
265 | + task->m_source = m_source; |
266 | + task->m_size = m_thumbnailsize; |
267 | + task->m_caller = this; |
268 | + m_currentTask = task; |
269 | + enqueueThumbnailTask(task); |
270 | + } |
271 | +} |
272 | + |
273 | +void QThumbnailer::cancelUpdateThumbnail() |
274 | +{ |
275 | + if (!m_currentTask.isNull()) { |
276 | + if (s_imageQueue.removeTask(m_currentTask.data()) || |
277 | + s_videoQueue.removeTask(m_currentTask.data()) ) { |
278 | + delete m_currentTask; |
279 | + m_currentTask.clear(); |
280 | + } |
281 | + } |
282 | +} |
283 | + |
284 | + |
285 | +/* Static methods implementation */ |
286 | + |
287 | +static const QString DEFAULT_VIDEO_ART("/usr/share/thumbnailer/icons/video_missing.png"); |
288 | +static const QString DEFAULT_ALBUM_ART("/usr/share/thumbnailer/icons/album_missing.png"); |
289 | + |
290 | +ThumbnailQueue QThumbnailer::s_videoQueue; |
291 | +ThumbnailQueue QThumbnailer::s_imageQueue; |
292 | +Thumbnailer QThumbnailer::s_thumbnailer; |
293 | +QMimeDatabase QThumbnailer::s_mimeDatabase; |
294 | + |
295 | +QString QThumbnailer::thumbnailPathForMedia(QString mediaPath, ThumbnailSize size) |
296 | +{ |
297 | + QString thumbnailPath; |
298 | + try { |
299 | + thumbnailPath = QString::fromStdString(s_thumbnailer.get_thumbnail(mediaPath.toStdString(), |
300 | + (ThumbnailSize)size)); |
301 | + } catch(std::runtime_error &e) { |
302 | + qWarning() << QString("Thumbnail retrieval for %1 failed:").arg(mediaPath).toLocal8Bit().constData() << e.what(); |
303 | + } |
304 | + |
305 | + if(thumbnailPath.isEmpty()) { |
306 | + QMimeType mime = s_mimeDatabase.mimeTypeForFile(mediaPath); |
307 | + if(mime.name().contains("audio")) { |
308 | + thumbnailPath = DEFAULT_ALBUM_ART; |
309 | + } else if(mime.name().contains("video")) { |
310 | + thumbnailPath = DEFAULT_VIDEO_ART; |
311 | + } |
312 | + } |
313 | + |
314 | + return thumbnailPath; |
315 | +} |
316 | + |
317 | +void QThumbnailer::enqueueThumbnailTask(ThumbnailTask* task) |
318 | +{ |
319 | + QMimeType mime = s_mimeDatabase.mimeTypeForFile(task->m_source.path()); |
320 | + if(mime.name().contains("image")) { |
321 | + s_imageQueue.appendTask(task); |
322 | + } else { |
323 | + s_videoQueue.appendTask(task); |
324 | + } |
325 | +} |
326 | |
327 | === added file 'plugins/Ubuntu/Thumbnailer/qthumbnailer.h' |
328 | --- plugins/Ubuntu/Thumbnailer/qthumbnailer.h 1970-01-01 00:00:00 +0000 |
329 | +++ plugins/Ubuntu/Thumbnailer/qthumbnailer.h 2015-02-25 18:25:10 +0000 |
330 | @@ -0,0 +1,114 @@ |
331 | +/* |
332 | + * Copyright 2015 Canonical Ltd. |
333 | + * |
334 | + * This program is free software; you can redistribute it and/or modify |
335 | + * it under the terms of the GNU Lesser General Public License as published by |
336 | + * the Free Software Foundation; version 3. |
337 | + * |
338 | + * This program is distributed in the hope that it will be useful, |
339 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
340 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
341 | + * GNU Lesser General Public License for more details. |
342 | + * |
343 | + * You should have received a copy of the GNU Lesser General Public License |
344 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
345 | + * |
346 | + * Authors: Florian Boucault <florian.boucault@canonical.com> |
347 | +*/ |
348 | + |
349 | +#ifndef THUMBNAILER_H |
350 | +#define THUMBNAILER_H |
351 | + |
352 | +#include <QtCore/QObject> |
353 | +#include <QtCore/QUrl> |
354 | +#include <QtCore/QMimeDatabase> |
355 | +#include <QtCore/QPointer> |
356 | +#include <QtCore/QSize> |
357 | +#include <QtQml/QQmlParserStatus> |
358 | +#include <thumbnailer.h> |
359 | +#include "thumbnailqueue.h" |
360 | + |
361 | +class ThumbnailTask; |
362 | + |
363 | +class QThumbnailer : public QObject, public QQmlParserStatus |
364 | +{ |
365 | + Q_OBJECT |
366 | + Q_INTERFACES(QQmlParserStatus) |
367 | + Q_DISABLE_COPY(QThumbnailer) |
368 | + |
369 | + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) |
370 | + Q_PROPERTY(QSize size READ size WRITE setSize NOTIFY sizeChanged) |
371 | + Q_PROPERTY(QUrl thumbnail READ thumbnail NOTIFY thumbnailChanged) |
372 | + |
373 | + friend class ThumbnailTask; |
374 | + |
375 | +public: |
376 | + QThumbnailer(QObject* parent=0); |
377 | + ~QThumbnailer(); |
378 | + |
379 | + // inherited from QQmlParserStatus |
380 | + void classBegin(); |
381 | + void componentComplete(); |
382 | + |
383 | + // getters and setters |
384 | + QUrl source() const; |
385 | + void setSource(QUrl source); |
386 | + QSize size() const; |
387 | + void setSize(QSize size); |
388 | + QUrl thumbnail() const; |
389 | + |
390 | +Q_SIGNALS: |
391 | + void sourceChanged(); |
392 | + void sizeChanged(); |
393 | + void thumbnailChanged(); |
394 | + |
395 | +protected: |
396 | + void updateThumbnail(); |
397 | + void cancelUpdateThumbnail(); |
398 | + static QString thumbnailPathForMedia(QString mediaPath, ThumbnailSize size); |
399 | + static void enqueueThumbnailTask(ThumbnailTask* task); |
400 | + |
401 | +protected Q_SLOTS: |
402 | + void setThumbnail(QString thumbnail); |
403 | + |
404 | +private: |
405 | + bool m_componentCompleted; |
406 | + QUrl m_source; |
407 | + QUrl m_thumbnail; |
408 | + QSize m_size; |
409 | + ThumbnailSize m_thumbnailsize; |
410 | + QPointer<ThumbnailTask> m_currentTask; |
411 | + |
412 | + // static class members shared accross all instances of QThumbnailer |
413 | + static ThumbnailQueue s_videoQueue; |
414 | + static ThumbnailQueue s_imageQueue; |
415 | + static Thumbnailer s_thumbnailer; |
416 | + static QMimeDatabase s_mimeDatabase; |
417 | +}; |
418 | + |
419 | +class ThumbnailTask : public QObject, public QRunnable |
420 | +{ |
421 | + Q_OBJECT |
422 | + |
423 | +public: |
424 | + virtual void run() { |
425 | + try { |
426 | + QString thumbnail = QThumbnailer::thumbnailPathForMedia(m_source.path(), m_size); |
427 | + Q_EMIT thumbnailPathRetrieved(thumbnail); |
428 | + } catch (std::exception &e) { |
429 | + // catch all exceptions cause it is not allowed to have unhandled |
430 | + // exceptions thrown from a worker thread |
431 | + } |
432 | + Q_EMIT finished(); |
433 | + } |
434 | + |
435 | + QUrl m_source; |
436 | + ThumbnailSize m_size; |
437 | + QPointer<QThumbnailer> m_caller; |
438 | + |
439 | +Q_SIGNALS: |
440 | + void thumbnailPathRetrieved(QString thumbnail); |
441 | + void finished(); |
442 | +}; |
443 | + |
444 | +#endif |
445 | |
446 | === added file 'plugins/Ubuntu/Thumbnailer/thumbnailqueue.cpp' |
447 | --- plugins/Ubuntu/Thumbnailer/thumbnailqueue.cpp 1970-01-01 00:00:00 +0000 |
448 | +++ plugins/Ubuntu/Thumbnailer/thumbnailqueue.cpp 2015-02-25 18:25:10 +0000 |
449 | @@ -0,0 +1,45 @@ |
450 | +/* |
451 | + * Copyright 2015 Canonical Ltd. |
452 | + * |
453 | + * This program is free software; you can redistribute it and/or modify |
454 | + * it under the terms of the GNU Lesser General Public License as published by |
455 | + * the Free Software Foundation; version 3. |
456 | + * |
457 | + * This program is distributed in the hope that it will be useful, |
458 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
459 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
460 | + * GNU Lesser General Public License for more details. |
461 | + * |
462 | + * You should have received a copy of the GNU Lesser General Public License |
463 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
464 | + * |
465 | + * Authors: Florian Boucault <florian.boucault@canonical.com> |
466 | +*/ |
467 | + |
468 | +#include "thumbnailqueue.h" |
469 | +#include "qthumbnailer.h" |
470 | + |
471 | +void ThumbnailQueue::appendTask(ThumbnailTask* task) |
472 | +{ |
473 | + connect(task, SIGNAL(thumbnailPathRetrieved(QString)), task->m_caller, SLOT(setThumbnail(QString))); |
474 | + connect(task, SIGNAL(finished()), this, SLOT(processNext())); |
475 | + m_queue.append(task); |
476 | + processNext(); |
477 | +} |
478 | + |
479 | +bool ThumbnailQueue::removeTask(ThumbnailTask* task) |
480 | +{ |
481 | + return m_queue.removeOne(task); |
482 | +} |
483 | + |
484 | +void ThumbnailQueue::processNext() |
485 | +{ |
486 | + if (m_queue.isEmpty()) { |
487 | + return; |
488 | + } |
489 | + |
490 | + ThumbnailTask* task = m_queue.takeFirst(); |
491 | + if (!m_threadPool.tryStart(task)) { |
492 | + m_queue.prepend(task); |
493 | + } |
494 | +} |
495 | |
496 | === added file 'plugins/Ubuntu/Thumbnailer/thumbnailqueue.h' |
497 | --- plugins/Ubuntu/Thumbnailer/thumbnailqueue.h 1970-01-01 00:00:00 +0000 |
498 | +++ plugins/Ubuntu/Thumbnailer/thumbnailqueue.h 2015-02-25 18:25:10 +0000 |
499 | @@ -0,0 +1,41 @@ |
500 | +/* |
501 | + * Copyright 2015 Canonical Ltd. |
502 | + * |
503 | + * This program is free software; you can redistribute it and/or modify |
504 | + * it under the terms of the GNU Lesser General Public License as published by |
505 | + * the Free Software Foundation; version 3. |
506 | + * |
507 | + * This program is distributed in the hope that it will be useful, |
508 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
509 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
510 | + * GNU Lesser General Public License for more details. |
511 | + * |
512 | + * You should have received a copy of the GNU Lesser General Public License |
513 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
514 | + * |
515 | + * Authors: Florian Boucault <florian.boucault@canonical.com> |
516 | +*/ |
517 | + |
518 | +#ifndef THUMBNAILQUEUE_H |
519 | +#define THUMBNAILQUEUE_H |
520 | + |
521 | +#include <QtCore/QObject> |
522 | +#include <QtCore/QThreadPool> |
523 | + |
524 | +class ThumbnailTask; |
525 | + |
526 | +class ThumbnailQueue : public QObject |
527 | +{ |
528 | + Q_OBJECT |
529 | + |
530 | +public: |
531 | + void appendTask(ThumbnailTask* task); |
532 | + bool removeTask(ThumbnailTask* task); |
533 | + Q_SLOT void processNext(); |
534 | + |
535 | +private: |
536 | + QThreadPool m_threadPool; |
537 | + QList<ThumbnailTask*> m_queue; |
538 | +}; |
539 | + |
540 | +#endif |
541 | |
542 | === modified file 'src/libthumbnailer.map' |
543 | --- src/libthumbnailer.map 2014-08-01 13:22:43 +0000 |
544 | +++ src/libthumbnailer.map 2015-02-25 18:25:10 +0000 |
545 | @@ -7,6 +7,7 @@ |
546 | "Thumbnailer::get_thumbnail(std::string const&, ThumbnailSize, ThumbnailPolicy)"; |
547 | "Thumbnailer::get_album_art(std::string const&, std::string const&, ThumbnailSize, ThumbnailPolicy)"; |
548 | "Thumbnailer::get_artist_art(std::string const&, std::string const&, ThumbnailSize, ThumbnailPolicy)"; |
549 | + "Thumbnailer::thumbnail_is_cached(std::string const&, ThumbnailSize)"; |
550 | }; |
551 | local: |
552 | extern "C++" { |
553 | |
554 | === modified file 'src/thumbnailer.cpp' |
555 | --- src/thumbnailer.cpp 2015-01-20 15:29:10 +0000 |
556 | +++ src/thumbnailer.cpp 2015-02-25 18:25:10 +0000 |
557 | @@ -37,6 +37,25 @@ |
558 | |
559 | using namespace std; |
560 | |
561 | +namespace { |
562 | + std::string absolute_path_from_filename(const std::string &filename) { |
563 | + string abspath; |
564 | + if(filename.empty()) { |
565 | + return ""; |
566 | + } |
567 | + if(filename[0] != '/') { |
568 | + auto cwd = getcwd(nullptr, 0); |
569 | + abspath += cwd; |
570 | + free(cwd); |
571 | + abspath += "/" + filename; |
572 | + } else { |
573 | + abspath = filename; |
574 | + } |
575 | + return abspath; |
576 | + } |
577 | +} |
578 | + |
579 | + |
580 | class ThumbnailerPrivate { |
581 | private: |
582 | random_device rnd; |
583 | @@ -214,18 +233,7 @@ |
584 | } |
585 | std::string Thumbnailer::get_thumbnail(const std::string &filename, ThumbnailSize desired_size, |
586 | ThumbnailPolicy policy) { |
587 | - string abspath; |
588 | - if(filename.empty()) { |
589 | - return ""; |
590 | - } |
591 | - if(filename[0] != '/') { |
592 | - auto cwd = getcwd(nullptr, 0); |
593 | - abspath += cwd; |
594 | - free(cwd); |
595 | - abspath += "/" + filename; |
596 | - } else { |
597 | - abspath = filename; |
598 | - } |
599 | + std::string abspath = absolute_path_from_filename(filename); |
600 | std::string estimate = p->cache.get_if_exists(abspath, desired_size); |
601 | if(!estimate.empty()) |
602 | return estimate; |
603 | @@ -322,3 +330,16 @@ |
604 | |
605 | return ""; |
606 | } |
607 | + |
608 | +bool Thumbnailer::thumbnail_is_cached(const std::string &filename, ThumbnailSize desired_size) { |
609 | + std::string abspath = absolute_path_from_filename(filename); |
610 | + std::string estimate = p->cache.get_if_exists(abspath, desired_size); |
611 | + if(!estimate.empty()) |
612 | + return false; |
613 | + |
614 | + if (p->cache.has_failure(abspath)) { |
615 | + return false; |
616 | + } |
617 | + |
618 | + return true; |
619 | +} |
620 | |
621 | === modified file 'tests/basic.cpp' |
622 | --- tests/basic.cpp 2014-09-25 09:43:53 +0000 |
623 | +++ tests/basic.cpp 2015-02-25 18:25:10 +0000 |
624 | @@ -168,6 +168,29 @@ |
625 | ASSERT_FALSE(file_exists(thumbfile3)); |
626 | } |
627 | |
628 | +static void is_cached_before_after_test(Thumbnailer &tn, string &imfile, ThumbnailSize size, bool always_needed) { |
629 | + bool is_cached; |
630 | + string thumbfile = tn.get_thumbnail(imfile, size); |
631 | + is_cached = tn.thumbnail_is_cached(imfile, size); |
632 | + ASSERT_EQ(is_cached, always_needed); |
633 | + unlink(thumbfile.c_str()); |
634 | + is_cached = tn.thumbnail_is_cached(imfile, size); |
635 | + ASSERT_TRUE(is_cached); |
636 | +} |
637 | + |
638 | +TEST(Thumbnailer, thumbnail_is_cached_size) { |
639 | + Thumbnailer tn; |
640 | + string srcimg(TESTIMAGE); |
641 | + string imfile("working_image.jpg"); |
642 | + copy_file(srcimg, imfile); |
643 | + |
644 | + is_cached_before_after_test(tn, imfile, TN_SIZE_SMALL, false); |
645 | + is_cached_before_after_test(tn, imfile, TN_SIZE_LARGE, false); |
646 | + is_cached_before_after_test(tn, imfile, TN_SIZE_XLARGE, false); |
647 | + /* when size is TN_SIZE_ORIGINAL generation will always happen */ |
648 | + is_cached_before_after_test(tn, imfile, TN_SIZE_ORIGINAL, true); |
649 | +} |
650 | + |
651 | TEST(Thumbnailer, no_image_cache) { |
652 | Thumbnailer tn; |
653 | string srcimg(TESTIMAGE); |
654 | |
655 | === added file 'tests/qml/testimage.jpg' |
656 | Binary files tests/qml/testimage.jpg 1970-01-01 00:00:00 +0000 and tests/qml/testimage.jpg 2015-02-25 18:25:10 +0000 differ |
657 | === modified file 'tests/qml/tst_image_provider.qml' |
658 | --- tests/qml/tst_image_provider.qml 2014-08-04 09:39:59 +0000 |
659 | +++ tests/qml/tst_image_provider.qml 2015-02-25 18:25:10 +0000 |
660 | @@ -30,13 +30,13 @@ |
661 | function test_albumart() { |
662 | var ctx = loadImage( |
663 | "image://albumart/artist=Gotye&album=Making%20Mirrors"); |
664 | - comparePixel(ctx, 0, 0, 242, 228, 209, 255); |
665 | + comparePixel(ctx, 0, 0, 242, 229, 212, 255); |
666 | } |
667 | |
668 | function test_artistart() { |
669 | var ctx = loadImage( |
670 | "image://artistart/artist=Gotye&album=Making%20Mirrors"); |
671 | - comparePixel(ctx, 0, 0, 242, 228, 209, 255); |
672 | + comparePixel(ctx, 0, 0, 249, 242, 236, 255); |
673 | } |
674 | |
675 | function loadImage(uri) { |
676 | |
677 | === added file 'tests/qml/tst_thumbnailer.qml' |
678 | --- tests/qml/tst_thumbnailer.qml 1970-01-01 00:00:00 +0000 |
679 | +++ tests/qml/tst_thumbnailer.qml 2015-02-25 18:25:10 +0000 |
680 | @@ -0,0 +1,97 @@ |
681 | +import QtQuick 2.0 |
682 | +import QtTest 1.0 |
683 | +import Ubuntu.Thumbnailer 0.1 |
684 | + |
685 | +Item { |
686 | + Image { |
687 | + id: image |
688 | + width: 200 |
689 | + height: 200 |
690 | + source: thumbnailer.thumbnail |
691 | + |
692 | + SignalSpy { |
693 | + id: imageSpy |
694 | + target: image |
695 | + signalName: "statusChanged" |
696 | + } |
697 | + } |
698 | + |
699 | + Thumbnailer { |
700 | + id: thumbnailer |
701 | + size { |
702 | + width: image.width |
703 | + height: image.height |
704 | + } |
705 | + } |
706 | + |
707 | + SignalSpy { |
708 | + id: thumbnailerSpy |
709 | + target: thumbnailer |
710 | + signalName: "thumbnailChanged" |
711 | + } |
712 | + |
713 | + Canvas { |
714 | + id: canvas |
715 | + width: image.width |
716 | + height: image.height |
717 | + renderStrategy: Canvas.Immediate |
718 | + renderTarget: Canvas.Image |
719 | + } |
720 | + |
721 | + TestCase { |
722 | + name: "ThumbnailerTests" |
723 | + when: windowShown |
724 | + |
725 | + function cleanup() { |
726 | + thumbnailer.source = ""; |
727 | + thumbnailerSpy.clear(); |
728 | + imageSpy.clear(); |
729 | + } |
730 | + |
731 | + function test_initialState() { |
732 | + compare(thumbnailerSpy.count, 0); |
733 | + compare(thumbnailer.size.width, 200); |
734 | + compare(thumbnailer.size.height, 200); |
735 | + compare(thumbnailer.thumbnail, ""); |
736 | + } |
737 | + |
738 | + function test_localjpeg() { |
739 | + thumbnailer.source = "testimage.jpg"; |
740 | + thumbnailerSpy.wait(); |
741 | + compare(thumbnailerSpy.count, 1); |
742 | + verify(thumbnailer.thumbnail != ""); |
743 | + tryCompare(image, "status", Image.Ready) |
744 | + |
745 | + var ctx = getContextForImage(image); |
746 | + comparePixel(ctx, 0, 0, 255, 1, 1, 255); |
747 | + comparePixel(ctx, 100, 100, 4, 2, 1, 255); |
748 | + } |
749 | + |
750 | + function test_nonExistingFile() { |
751 | + thumbnailer.source = "idontexist.jpg"; |
752 | + compare(thumbnailerSpy.count, 0); |
753 | + compare(thumbnailer.thumbnail, ""); |
754 | + tryCompare(image, "status", Image.Null) |
755 | + } |
756 | + |
757 | + function getContextForImage(image) { |
758 | + var ctx = canvas.getContext("2d"); |
759 | + ctx.drawImage(image, 0, 0); |
760 | + return ctx; |
761 | + } |
762 | + |
763 | + function comparePixel(ctx,x,y,r,g,b,a, d) { |
764 | + var c = ctx.getImageData(x,y,1,1).data; |
765 | + if (d === undefined) |
766 | + d = 0; |
767 | + r = Math.round(r); |
768 | + g = Math.round(g); |
769 | + b = Math.round(b); |
770 | + a = Math.round(a); |
771 | + var notSame = Math.abs(c[0]-r)>d || Math.abs(c[1]-g)>d || Math.abs(c[2]-b)>d || Math.abs(c[3]-a)>d; |
772 | + if (notSame) |
773 | + qtest_fail('Pixel compare fail:\nactual :[' + c[0]+','+c[1]+','+c[2]+','+c[3] + ']\nexpected:['+r+','+g+','+b+','+a+'] +/- '+d, 1); |
774 | + |
775 | + } |
776 | + } |
777 | +} |
FAILED: Continuous integration, rev:137 jenkins. qa.ubuntu. com/job/ thumbnailer- ci/136/ jenkins. qa.ubuntu. com/job/ thumbnailer- vivid-amd64- ci/30/console jenkins. qa.ubuntu. com/job/ thumbnailer- vivid-armhf- ci/28/console jenkins. qa.ubuntu. com/job/ thumbnailer- vivid-i386- ci/25/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/thumbnailer -ci/136/ rebuild
http://