1
2
3
4
5
6
7
8 package com.buckosoft.PicMan.image;
9
10 import java.awt.Graphics2D;
11 import java.awt.Image;
12 import java.awt.image.BufferedImage;
13 import java.io.File;
14 import java.io.IOException;
15 import java.util.Date;
16 import java.util.HashMap;
17 import java.util.Iterator;
18 import java.util.List;
19
20 import javax.imageio.ImageIO;
21 import javax.imageio.ImageReader;
22 import javax.imageio.ImageWriter;
23 import javax.imageio.stream.ImageInputStream;
24 import javax.imageio.stream.ImageOutputStream;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28
29 import com.buckosoft.PicMan.business.PicManFacade;
30 import com.buckosoft.PicMan.domain.JobLogEntry;
31 import com.buckosoft.PicMan.domain.Pic;
32 import com.buckosoft.PicMan.domain.Thumbnail;
33
34
35
36
37
38
39
40
41
42 public class ThumbCache {
43 private static final boolean DEBUG = false;
44 private static final boolean DEBUGHITS = false;
45 protected final Log logger = LogFactory.getLog(getClass());
46
47 private PicManFacade pmf;
48
49 private boolean active = false;
50 private String thumbExt = "jpg";
51 private int cachedHeight = 0;
52 private String cacheDirectory;
53 private int maxEntriesPerCache = 1000;
54 private int currentCacheFillDir = 0;
55 private int currentCacheFillCount = 0;
56 private boolean buildThumbCache = false;
57 private boolean mosaicThumbCache = false;
58 private int mosaicSet = 0;
59
60 private HashMap<Pic, Thumbnail> mosaicSuperCache = null;
61 private int mosaicSuperCacheMaxSize = 6300;
62
63
64
65
66 public void setPicMan(PicManFacade pmf) {
67 this.pmf = pmf;
68
69 }
70
71
72
73
74 public boolean isBuildThumbCache() {
75 return buildThumbCache;
76 }
77
78
79
80
81 public int getCachedHeight() {
82 return(this.cachedHeight);
83 }
84
85
86
87
88
89 public int getMosaicSet() {
90 return mosaicSet;
91 }
92
93
94
95
96 public void setMosaicSet(int mosaicSet) {
97 this.mosaicSet = mosaicSet;
98 this.mosaicThumbCache = true;
99
100 }
101
102
103
104
105 public int getMosaicSuperCacheMaxSize() {
106 return mosaicSuperCacheMaxSize;
107 }
108
109
110
111 public void release() {
112 this.mosaicSuperCache = null;
113 }
114
115
116
117 public void setMosaicSuperCacheMaxSize(int mosaicSuperCacheMaxSize) {
118 this.mosaicSuperCacheMaxSize = mosaicSuperCacheMaxSize;
119 }
120
121
122
123
124 public void setBuildThumbCache(boolean buildThumbCache) {
125 this.buildThumbCache = buildThumbCache;
126 }
127
128
129
130
131
132
133
134 public Thumbnail getThumbNail(Pic pic, int height) {
135 if (!active)
136 return(null);
137
138
139 if (height > cachedHeight)
140 return(null);
141 if (!this.mosaicThumbCache && pic.getCacheDir() == 0)
142 return(null);
143 if (this.mosaicSuperCache != null && this.mosaicSuperCache.containsKey(pic))
144 return(this.mosaicSuperCache.get(pic));
145 File f;
146 if (this.mosaicThumbCache)
147 f = new File(this.cacheDirectory,
148 pic.getName()+ "." + thumbExt);
149 else
150 f = new File(this.cacheDirectory + File.separator + pic.getCacheDir(),
151 pic.getName()+ "." + thumbExt);
152 Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(thumbExt);
153 ImageReader reader = (ImageReader)readers.next();
154 BufferedImage bi;
155 if (DEBUGHITS)
156 logger.info("read: '" + f + "'");
157 ImageInputStream iis = null;
158 try {
159 iis = ImageIO.createImageInputStream(f);
160 if (DEBUGHITS)
161 logger.info("cache " + (iis != null ? "hit " : "miss ") + pic.getName());
162 if (iis == null) {
163 return(null);
164 }
165 reader.setInput(iis, true);
166 bi = reader.read(0);
167 iis.close();
168 } catch (IllegalStateException ise) {
169 if (DEBUG)
170 logger.info("caught IllegalStateException");
171 bi = null;
172 try {
173 iis.close();
174 } catch (IOException e) {}
175 return(null);
176 } catch (Exception e) {
177 if (DEBUG)
178 logger.info("caught Exception");
179 bi = null;
180 try {
181 iis.close();
182 } catch (IOException e1) {}
183 return(null);
184 }
185
186
187
188 if (height < cachedHeight) {
189 double dW = ((double)height/(double)bi.getHeight()) * bi.getWidth();
190 int newW = (int)dW;
191 BufferedImage small = new BufferedImage(newW, height, BufferedImage.TYPE_INT_BGR);
192 Graphics2D g;
193 g = small.createGraphics();
194 g.drawImage(bi.getScaledInstance(-1, height, Image.SCALE_SMOOTH), null, null);
195 bi = small;
196 }
197
198 Thumbnail tn;
199 tn = new Thumbnail();
200 tn.setName(pic.getName());
201 tn.setImage(bi);
202 bi = null;
203 if (this.mosaicSuperCache != null && this.mosaicSuperCache.size() < this.mosaicSuperCacheMaxSize)
204 this.mosaicSuperCache.put(pic, tn);
205 return(tn);
206 }
207
208
209
210
211
212 public void addToCache(Pic pic, Thumbnail tn) {
213 if (tn.isXThumb())
214 return;
215 if (!this.active)
216 return;
217
218 boolean savePic = false;
219
220 if (tn.getName() == null || tn.getName().length() < 1)
221 throw new RuntimeException("Can't add thumb to cache with no name");
222 if (tn.getImage().getHeight() != cachedHeight)
223 return;
224 if (!this.mosaicThumbCache && pic.getCacheDir() == 0) {
225 pic.setCacheDir(this.currentCacheFillDir);
226 savePic = true;
227 }
228 File file;
229 if (this.mosaicThumbCache)
230 file = new File(this.cacheDirectory,
231 tn.getName() + "." + thumbExt);
232 else
233 file = new File(this.cacheDirectory + File.separator + this.currentCacheFillDir,
234 tn.getName() + "." + thumbExt);
235 if (!file.exists()) {
236 Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(thumbExt);
237 ImageWriter writer = (ImageWriter)writers.next();
238 if (DEBUGHITS)
239 logger.info("Writing '" + file.getPath() + "'");
240 ImageOutputStream oos = null;
241 try {
242 oos = ImageIO.createImageOutputStream(file);
243 writer.setOutput(oos);
244 writer.write(tn.getImage());
245 oos.close();
246 } catch (IOException e) {
247 if (oos != null)
248 try {
249 oos.close();
250 } catch (IOException e1) {
251 }
252 Exception ex = new Exception("Failed to save cache thumb", e);
253 pmf.addError(ex);
254 return;
255 }
256 }
257 if (savePic) {
258 pmf.getDB().updatePic(pic);
259 this.currentCacheFillCount++;
260 if (this.currentCacheFillCount >= this.maxEntriesPerCache) {
261 this.currentCacheFillDir++;
262 prepareCacheDir();
263 this.currentCacheFillCount = pmf.getDB().getPicThumbCacheFillCount(this.currentCacheFillDir);
264 if (DEBUGHITS)
265 logger.info("getPicThumbCacheFillCount returned " + this.currentCacheFillCount);
266 }
267 }
268 }
269
270
271 public void setupCache(String cacheDir, int cacheHeight, int maxEntriesPerCache) {
272 this.cacheDirectory = cacheDir;
273 this.cachedHeight = cacheHeight;
274 this.maxEntriesPerCache = maxEntriesPerCache;
275 this.active = pmf.getDB().getSystem().isUseThumbCache();
276 if (!this.active)
277 return;
278
279 File f;
280 f = new File(this.cacheDirectory);
281 if (!f.isDirectory()) {
282 JobLogEntry jle = new JobLogEntry();
283 jle.setType(JobLogEntry.INFO);
284 jle.setName("Create Thumb cache");
285 if (!f.mkdirs()) {
286 jle.setName("Failed to create Thumb cache");
287 jle.setError();
288 }
289 jle.setEndTime();
290 pmf.addJobToLog(jle);
291 if (jle.isError())
292 return;
293 }
294 this.currentCacheFillDir = pmf.getDB().getPicMaxThumbCacheDirUsed();
295 if (DEBUG)
296 logger.info("getPicMaxThumbCacheDirUsed returned " + this.currentCacheFillDir);
297 if (this.currentCacheFillDir == 0)
298 this.currentCacheFillDir = 1;
299 this.currentCacheFillCount = pmf.getDB().getPicThumbCacheFillCount(this.currentCacheFillDir);
300 if (DEBUG)
301 logger.info("getPicThumbCacheFillCount returned " + this.currentCacheFillCount);
302 prepareCacheDir();
303 }
304
305 private void prepareCacheDir() {
306
307
308 File dir = new File(this.cacheDirectory + File.separator + this.currentCacheFillDir);
309 if (!dir.isDirectory()) {
310 if (!dir.mkdir()) {
311
312 }
313 }
314 }
315
316
317
318
319
320
321 public void deleteCache() {
322 JobLogEntry jle = new JobLogEntry();
323 if (!mosaicThumbCache) {
324 jle.setType(JobLogEntry.INFO);
325 jle.setName("Delete Cache");
326 pmf.addJobToLog(jle);
327 }
328 File f = new File(this.cacheDirectory);
329 _deleteCache(f);
330 if (mosaicThumbCache)
331 return;
332 List<Pic> list = pmf.getDB().getPics();
333 Iterator<Pic> iter = list.iterator();
334 Pic pic;
335 while (iter.hasNext()) {
336 pic = iter.next();
337 if (pic.getCacheDir() != 0) {
338 pic.setCacheDir(0);
339 pmf.getDB().updatePic(pic);
340 }
341 }
342 jle.setEndTime(new Date());
343 }
344
345 private void _deleteCache(File f) {
346 File[] list = f.listFiles();
347 if (list == null)
348 return;
349 File ff;
350 int i;
351 for (i=0; i<list.length; i++) {
352 ff = list[i];
353 if (ff.isDirectory())
354 _deleteCache(ff);
355 boolean res = ff.delete();
356 if (!res)
357 ff.delete();
358 if (DEBUG) {
359 if (!res)
360 logger.info("Failed to delete '" + ff.getName() + "'");
361 }
362 }
363 }
364
365
366
367 public void batchBuildCache() throws Exception {
368 JobLogEntry jle = new JobLogEntry();
369 jle.setType(JobLogEntry.INFO);
370 jle.setName("Build Cache");
371 pmf.addJobToLog(jle);
372 List<Pic> list;
373 if (this.mosaicThumbCache)
374 list = pmf.getDB().getPics(pmf.getDB().getSet(this.mosaicSet), this.cachedHeight);
375 else
376 list = pmf.getDB().getPics();
377 Iterator<Pic> iter = list.iterator();
378 Pic pic = null;
379 picProcessing = 0;
380 try {
381 while (iter.hasNext()) {
382 pic = iter.next();
383 picProcessing++;
384 if (pic.getRid() == 0)
385 continue;
386 if (this.mosaicThumbCache) {
387 if (DEBUG)
388 logger.info("Create Mosaic cache entry for '" + pic.getName() + "'");
389 Thumbnail tn = pmf.getPicReader().getThumbNail(pic, this.cachedHeight, null);
390 if (this.mosaicThumbCache)
391 this.addToCache(pic, tn);
392
393 }
394 else if (pic.getCacheDir() == 0) {
395 if (DEBUG)
396 logger.info("Create cache entry for '" + pic.getName() + "'");
397 pmf.getPicReader().getThumbNail(pic, this.cachedHeight, null);
398 }
399 }
400 } catch (Exception e) {
401 logger.error("batchBuildCache failed on " + pic.getName());
402 jle.setEndTime(new Date());
403 throw new Exception("batchBuildCache failed on " + pic.getName(), e);
404 }
405 jle.setEndTime(new Date());
406 }
407
408 private int picProcessing = 0;
409
410
411
412
413
414 public int getPicProcessing() {
415 return(picProcessing);
416 }
417 }