e27ab1e608703ccc17a8835e742e3a1f849b25dd
[Portfolio.git] / manipulation.py
1 # -*- coding: utf-8 -*-
2 ####################################################
3 # Copyright © 2009 Luxia SAS. All rights reserved. #
4 # #
5 # Contributors: #
6 # - Benoît Pin <pinbe@luxia.fr> #
7 ####################################################
8 """ Image threaded batch computation module
9
10 $Id: manipulation.py 1391 2009-09-16 23:36:05Z pin $
11 $URL: http://svn.luxia.fr/svn/labo/projects/zope/Portfolio/trunk/manipulation.py $
12 """
13
14 import threading
15 import logging
16 import atexit
17 import time
18 import Zope2
19 from math import ceil
20 import transaction
21 from ZODB.POSException import ConflictError
22 from cStringIO import StringIO
23
24 console = logging.getLogger('[manipulation thread]')
25
26 class ImageQueueProcessorThread(threading.Thread) :
27 """This thread is started at zope startup
28 """
29
30 __stopped = False
31
32 def __init__(self, portal_path, itemPath):
33 threading.Thread.__init__(self)
34 self.app = app = Zope2.app()
35 self.portal = portal = app.unrestrictedTraverse(portal_path)
36 self.imgTool = portal.portal_image_manipulation
37 self.queue = []
38 if itemPath :
39 self.queueAdd(itemPath)
40 else :
41 ctool = portal.portal_catalog
42 brains = ctool.unrestrictedSearchResults(portal_type='Photo', tiles_available=0)
43 for b in brains :
44 self.queueAdd(b.getPath())
45
46 @property
47 def queueSize(self) :
48 return len(self.queue)
49
50 def queueAdd(self, itemPath) :
51 self.queue.append(itemPath)
52
53 def run(self) :
54 console.info('process started.')
55 atexit.register(self.stop)
56 while not self.__stopped and self.queueSize :
57 self._process()
58 console.info('process finished.')
59
60 def stop(self):
61 console.info('process stopped.')
62 self.__stopped = True
63
64 def _process(self) :
65
66 path = self.queue.pop(0)
67
68 try :
69 p = self.app.unrestrictedTraverse(path)
70 except KeyError :
71 console.warn('deleted during processing: %s' % path)
72 return
73
74 console.info('%d : %s' % (self.queueSize, p.absolute_url()))
75
76 try :
77 if not hasattr(p, 'thumbnail'):
78 p.makeThumbnail()
79 # print 'make thumbnail'
80
81 for size in ((500, 500), (600, 600), (800, 800)) :
82 # print 'resize at', size
83 p._getResizedImage(size, True)
84 transaction.commit()
85
86 zMin = p.tiles_min_zoom
87 zMax = p.tiles_max_zoom
88 zStep = p.tiles_step_zoom
89 levels = range(zMin, zMax + zStep, zStep)
90 zooms = [l/100. for l in levels]
91 todo = set(zooms) - set(p._tiles.keys())
92 if todo :
93 if p.tileGenerationLock.locked() :
94 console.info('skip %s: already tiling.' % p.absolute_url())
95 return
96
97 p.tileGenerationLock.acquire()
98 zooms = list(todo)
99 zooms.sort()
100 ppm = None
101 try :
102 ppm = p._getPPM()
103 for zoom in zooms :
104
105 # print 'tiling at', zoom
106 if zoom < 1 :
107 rppm = ppm.resize(ratio=zoom)
108 else :
109 rppm = ppm
110 p._makeTilesAt(zoom, rppm)
111 del rppm
112 transaction.commit()
113 finally :
114 del ppm
115 p.tileGenerationLock.release()
116
117 try :
118 delattr(p, '_v__methodResultsCache')
119 except AttributeError:
120 pass
121
122 p.tiles_available = 1
123 p.reindexObject(idxs=['tiles_available'])
124 transaction.commit()
125
126 except ConflictError :
127 console.warn('Resync after ZODB ConflicError')
128 transaction.abort()
129 self.portal._p_jar.sync()
130 self.queueAdd(path)
131 return
132 except :
133 p.tiles_available = -1
134 import traceback
135 out = StringIO()
136 traceback.print_exc(None, out)
137 console.error(out.getvalue())