10 # FOR A PARTICULAR PURPOSE. |
10 # FOR A PARTICULAR PURPOSE. |
11 # |
11 # |
12 |
12 |
13 __docformat__ = 'restructuredtext' |
13 __docformat__ = 'restructuredtext' |
14 |
14 |
|
15 import logging |
15 import re |
16 import re |
16 |
17 |
17 import transaction |
18 import transaction |
18 from BTrees import OOBTree |
19 from BTrees import OOBTree |
19 from persistent.dict import PersistentDict |
20 from persistent.dict import PersistentDict |
20 from pyramid.events import subscriber |
21 from pyramid.events import subscriber |
21 from pyramid.threadlocal import get_current_registry |
22 from pyramid.threadlocal import get_current_registry |
22 from zope.interface import Interface, alsoProvides |
23 from zope.interface import Interface, alsoProvides |
23 from zope.lifecycleevent import ObjectAddedEvent, ObjectCreatedEvent, ObjectRemovedEvent |
24 from zope.lifecycleevent import IObjectRemovedEvent, ObjectAddedEvent, ObjectCreatedEvent, ObjectRemovedEvent |
24 from zope.location import locate |
25 from zope.location import locate |
25 from zope.traversing.interfaces import ITraversable |
26 from zope.traversing.interfaces import ITraversable |
26 |
27 |
27 from pyams_file.file import FileFactory |
28 from pyams_file.file import FileFactory |
28 from pyams_file.interfaces import IFileModifiedEvent, IImage, IThumbnailFile, IThumbnailer, IThumbnails, IWatermarker |
29 from pyams_file.interfaces import IFileModifiedEvent, IImage, IMediaFile, IThumbnailFile, IThumbnailer, IThumbnails, \ |
|
30 IWatermarker |
29 from pyams_file.zmi.image import render_image |
31 from pyams_file.zmi.image import render_image |
30 from pyams_utils.adapter import ContextAdapter, ContextRequestViewAdapter, adapter_config, get_annotation_adapter |
32 from pyams_utils.adapter import ContextAdapter, ContextRequestViewAdapter, adapter_config, get_annotation_adapter |
31 from pyams_utils.interfaces.tales import ITALESExtension |
33 from pyams_utils.interfaces.tales import ITALESExtension |
32 from pyams_utils.registry import query_utility |
34 from pyams_utils.registry import query_utility |
33 from pyams_utils.request import check_request |
35 from pyams_utils.request import check_request |
|
36 |
|
37 |
|
38 logger = logging.getLogger('PyAMS (file)') |
34 |
39 |
35 |
40 |
36 THUMBNAIL_ANNOTATIONS_KEY = 'pyams_file.image.thumbnails' |
41 THUMBNAIL_ANNOTATIONS_KEY = 'pyams_file.image.thumbnails' |
37 THUMBNAIL_GEOMETRY_KEY = 'pyams_file.image.geometry' |
42 THUMBNAIL_GEOMETRY_KEY = 'pyams_file.image.geometry' |
38 |
43 |
121 return '{0}x{1}'.format(*size) |
126 return '{0}x{1}'.format(*size) |
122 else: |
127 else: |
123 return None, None |
128 return None, None |
124 |
129 |
125 def get_selection(self, selection_name, format=None): |
130 def get_selection(self, selection_name, format=None): |
|
131 logger.debug(">>> Requested thumbnail selection: {}".format(selection_name)) |
126 if selection_name in self.thumbnails: |
132 if selection_name in self.thumbnails: |
127 return self.thumbnails[selection_name] |
133 return self.thumbnails[selection_name] |
128 geometry = self.get_geometry(selection_name) |
134 geometry = self.get_geometry(selection_name) |
129 registry = get_current_registry() |
135 if geometry == IThumbnailer(self.image).get_default_geometry(): |
130 thumbnailer = registry.queryAdapter(self.image, IThumbnailer, name=selection_name) |
136 return self.image |
131 if thumbnailer is not None: |
137 else: |
132 selection = thumbnailer.create_thumbnail(geometry, format) |
138 registry = get_current_registry() |
133 if selection is not None: |
139 thumbnailer = registry.queryAdapter(self.image, IThumbnailer, name=selection_name) |
134 if isinstance(selection, tuple): |
140 if thumbnailer is not None: |
135 selection, format = selection |
141 selection = thumbnailer.create_thumbnail(geometry, format) |
136 else: |
142 if selection is not None: |
137 format = 'jpeg' |
143 if isinstance(selection, tuple): |
138 selection = FileFactory(selection) |
144 selection, format = selection |
139 alsoProvides(selection, IThumbnailFile) |
145 else: |
140 registry.notify(ObjectCreatedEvent(selection)) |
146 format = 'jpeg' |
141 self.thumbnails[selection_name] = selection |
147 selection = FileFactory(selection) |
142 selection_size = selection.get_image_size() |
148 alsoProvides(selection, IThumbnailFile) |
143 locate(selection, self.image, |
149 registry.notify(ObjectCreatedEvent(selection)) |
144 '++thumb++{0}:{1}x{2}.{3}'.format(selection_name, |
150 self.thumbnails[selection_name] = selection |
145 selection_size[0], |
151 selection_size = selection.get_image_size() |
146 selection_size[1], |
152 locate(selection, self.image, |
147 format)) |
153 '++thumb++{0}:{1}x{2}.{3}'.format(selection_name, |
148 registry.notify(ObjectAddedEvent(selection)) |
154 selection_size[0], |
149 return selection |
155 selection_size[1], |
|
156 format)) |
|
157 logger.debug(" > Generated thumbnail selection: {}".format(selection.__name__)) |
|
158 registry.notify(ObjectAddedEvent(selection)) |
|
159 return selection |
150 |
160 |
151 def get_thumbnail(self, thumbnail_name, format=None, watermark=None): |
161 def get_thumbnail(self, thumbnail_name, format=None, watermark=None): |
|
162 logger.debug(">>> Requested thumbnail: {}".format(thumbnail_name)) |
152 # check for existing thumbnail |
163 # check for existing thumbnail |
153 if thumbnail_name in self.thumbnails: |
164 if thumbnail_name in self.thumbnails: |
154 return self.thumbnails[thumbnail_name] |
165 return self.thumbnails[thumbnail_name] |
155 # check for selection thumbnail, in "selection:size" format |
166 # check for selection thumbnail, in "selection:size" format |
156 if ':' in thumbnail_name: |
167 if ':' in thumbnail_name: |
202 '++thumb++{0}{1}{2}x{3}.{4}'.format(thumbnailer_name, |
213 '++thumb++{0}{1}{2}x{3}.{4}'.format(thumbnailer_name, |
203 ':' if thumbnailer_name else '', |
214 ':' if thumbnailer_name else '', |
204 thumbnail_size[0], |
215 thumbnail_size[0], |
205 thumbnail_size[1], |
216 thumbnail_size[1], |
206 format)) |
217 format)) |
|
218 logger.debug(" < Generated thumbnail: {}".format(thumbnail_image.__name__)) |
207 registry.notify(ObjectAddedEvent(thumbnail_image)) |
219 registry.notify(ObjectAddedEvent(thumbnail_image)) |
208 return thumbnail_image |
220 return thumbnail_image |
209 |
221 |
210 def delete_thumbnail(self, thumbnail_name): |
222 def delete_thumbnail(self, thumbnail_name): |
211 if thumbnail_name in self.thumbnails: |
223 if thumbnail_name in self.thumbnails: |
212 thumbnail_image = self.thumbnails[thumbnail_name] |
224 thumbnail_image = self.thumbnails[thumbnail_name] |
213 registry = check_request().registry |
225 registry = get_current_registry() |
214 registry.notify(ObjectRemovedEvent(thumbnail_image)) |
226 registry.notify(ObjectRemovedEvent(thumbnail_image)) |
215 del self.thumbnails[thumbnail_name] |
227 del self.thumbnails[thumbnail_name] |
|
228 logger.debug(">>> Removed thumbnail: {}".format(thumbnail_name)) |
216 |
229 |
217 def clear_thumbnails(self): |
230 def clear_thumbnails(self): |
218 [self.delete_thumbnail(thumbnail_name) for thumbnail_name in list(self.thumbnails.keys())] |
231 [self.delete_thumbnail(thumbnail_name) for thumbnail_name in list(self.thumbnails.keys())] |
219 |
232 |
220 |
233 |
221 @subscriber(IFileModifiedEvent, context_selector=IImage) |
234 @subscriber(IFileModifiedEvent, context_selector=IMediaFile) |
222 def handle_modified_file(event): |
235 @subscriber(IObjectRemovedEvent, context_selector=IMediaFile) |
223 thumbnail = IThumbnails(event.object) |
236 def handle_modified_image(event): |
224 thumbnail.clear_geometries() |
237 """Clear thumbnails when an image is updated or removed""" |
225 thumbnail.clear_thumbnails() |
238 thumbnails = IThumbnails(event.object, None) |
|
239 if thumbnails is not None: |
|
240 thumbnails.clear_geometries() |
|
241 thumbnails.clear_thumbnails() |
226 |
242 |
227 |
243 |
228 @adapter_config(name='thumb', context=IImage, provides=ITraversable) |
244 @adapter_config(name='thumb', context=IImage, provides=ITraversable) |
229 class ThumbnailTraverser(ContextAdapter): |
245 class ThumbnailTraverser(ContextAdapter): |
230 """++thumb++ namespace traverser""" |
246 """++thumb++ namespace traverser""" |
259 return IThumbnails(context, None) |
275 return IThumbnails(context, None) |
260 |
276 |
261 |
277 |
262 @adapter_config(name='thumbnail', context=(Interface, Interface, Interface), provides=ITALESExtension) |
278 @adapter_config(name='thumbnail', context=(Interface, Interface, Interface), provides=ITALESExtension) |
263 class ThumbnailExtension(ContextRequestViewAdapter): |
279 class ThumbnailExtension(ContextRequestViewAdapter): |
264 """extension:thumbnail(image, width, height) TALES extension |
280 """extension:thumbnail(image, width, height, css_class, img_class) TALES extension |
265 |
281 |
266 This TALES extension doesn't return an adapter but HTML code matching given image and dimensions. |
282 This TALES extension doesn't return an adapter but HTML code matching given image and dimensions. |
267 If image is a classic image, an "img" tag with source to thumbnail of required size is returned. |
283 If image is a classic image, an "img" tag with source to thumbnail of required size is returned. |
268 If image in an SVG image, a "div" is returned containing whole SVG data of given image. |
284 If image in an SVG image, a "div" is returned containing whole SVG data of given image. |
269 """ |
285 """ |