Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
936 | dev | 1 | package ak.photoalbum.webapp; |
2 | |||
3 | import java.util.List; |
||
4 | import java.util.ArrayList; |
||
5 | import java.util.Arrays; |
||
6 | import java.util.Comparator; |
||
7 | import java.util.StringTokenizer; |
||
1242 | dev | 8 | import java.util.Map; |
9 | import java.util.Hashtable; |
||
936 | dev | 10 | import java.io.File; |
11 | import java.io.IOException; |
||
12 | import java.io.FileInputStream; |
||
1242 | dev | 13 | import java.io.BufferedReader; |
14 | import java.io.InputStreamReader; |
||
936 | dev | 15 | import java.io.OutputStream; |
16 | import java.io.FileNotFoundException; |
||
17 | import java.net.URLEncoder; |
||
1242 | dev | 18 | import org.xml.sax.SAXException; |
19 | import org.apache.commons.digester.Digester; |
||
936 | dev | 20 | import org.apache.log4j.Logger; |
21 | import ak.photoalbum.images.Thumbnailer; |
||
22 | import ak.photoalbum.images.ThumbnailPosition; |
||
23 | import ak.photoalbum.util.FileUtils; |
||
24 | import ak.photoalbum.util.FileNameComparator; |
||
25 | import ak.photoalbum.util.ImagesFilter; |
||
26 | |||
27 | public class Logic |
||
28 | { |
||
1242 | dev | 29 | protected static final int DEFAULT_COLUMNS = 5; |
30 | protected static final int DEFAULT_ROWS = 4; |
||
936 | dev | 31 | protected static final String URL_ENCODING = "UTF-8"; |
1242 | dev | 32 | protected static final String META_FILE_NAME = "meta.xml"; // FIXME make configurable (?) |
936 | dev | 33 | |
34 | protected Logger logger; |
||
35 | protected Thumbnailer thumbnailer; |
||
1247 | dev | 36 | protected ConfigRoot config; |
936 | dev | 37 | protected File imagesRoot; |
38 | protected int columns = DEFAULT_COLUMNS; |
||
1242 | dev | 39 | protected int rows = DEFAULT_ROWS; |
936 | dev | 40 | protected ImagesFilter imagesFilter; |
41 | protected Comparator fileNameComparator = new FileNameComparator(true); |
||
1247 | dev | 42 | protected Digester configDigester = createConfigDigester(); |
1242 | dev | 43 | protected Digester metaDigester = createMetaDigester(); |
44 | protected Map metaInfos = new Hashtable(); // <File, MetaInfo> |
||
936 | dev | 45 | |
46 | protected Logic() |
||
47 | { |
||
48 | this.logger = Logger.getLogger(this.getClass()); |
||
49 | } |
||
50 | |||
1247 | dev | 51 | protected Digester createConfigDigester() |
936 | dev | 52 | { |
1247 | dev | 53 | /* |
54 | 4 <uri>children</uri> |
||
55 | 5 <images-root>/home/data/photos</images-root> |
||
56 | 6 <cache-dir>/home/data/photos/.cache</cache-dir> |
||
57 | 7 <thumbnail-format>jpg</thumbnail-format> |
||
58 | 8 <columns>6</columns> |
||
59 | 9 <rows>5</rows> |
||
60 | 10 <images-mask>*.jpeg</images-mask> |
||
61 | 11 <images-mask>*.jpg</images-mask> |
||
62 | 12 <dir-thumbnail> |
||
63 | 13 <template>images/dir_template.gif</template> |
||
64 | 14 <thumbnail> |
||
65 | 15 <left>5</left> |
||
66 | 16 <top>9</top> |
||
67 | 17 <width>40</width> |
||
68 | 18 <height>40</height> |
||
69 | 19 <align>center</align> |
||
70 | 20 <valign>center</valign> |
||
71 | */ |
||
72 | Digester digester = new Digester(); |
||
73 | digester.setValidating(false); |
||
74 | |||
75 | digester.addObjectCreate("photos", ConfigRoot.class); |
||
76 | |||
77 | digester.addObjectCreate("photos/branch", ConfigBranch.class); |
||
78 | digester.addBeanPropertySetter("photos/branch/uri", "uri"); |
||
79 | digester.addBeanPropertySetter("photos/branch/images-root", "imagesRoot"); |
||
80 | digester.addBeanPropertySetter("photos/branch/cache-dir", "cacheDir"); |
||
81 | digester.addBeanPropertySetter("photos/branch/thumbnail-format", "thumbnailFormat"); |
||
82 | digester.addBeanPropertySetter("photos/branch/columns", "columns"); |
||
83 | digester.addBeanPropertySetter("photos/branch/rows", "rows"); |
||
84 | |||
85 | // FIXME images-mask |
||
86 | |||
87 | digester.addSetNext("photos/branch", "addBranch"); |
||
88 | /* digester.addBeanPropertySetter("meta/item/subtitle", "subtitle"); |
||
89 | digester.addSetProperties("meta/item/subtitle", "mime", "subtitleMime"); |
||
90 | digester.addBeanPropertySetter("meta/item/comment", "comment"); |
||
91 | digester.addSetProperties("meta/item/comment", "mime", "commentMime"); |
||
92 | digester.addSetNext("photos/branch", "addItem"); |
||
93 | */ |
||
94 | return digester; |
||
95 | } |
||
96 | |||
97 | protected Digester createMetaDigester() |
||
98 | { |
||
99 | Digester digester = new Digester(); |
||
100 | digester.setValidating(false); |
||
101 | |||
102 | digester.addObjectCreate("meta", MetaInfo.class); |
||
103 | |||
104 | digester.addObjectCreate("meta/item", MetaInfoItem.class); |
||
105 | digester.addSetProperties("meta/item", "id", "id"); |
||
106 | digester.addBeanPropertySetter("meta/item/title", "title"); |
||
107 | digester.addBeanPropertySetter("meta/item/subtitle", "subtitle"); |
||
108 | digester.addSetProperties("meta/item/subtitle", "mime", "subtitleMime"); |
||
109 | digester.addBeanPropertySetter("meta/item/comment", "comment"); |
||
110 | digester.addSetProperties("meta/item/comment", "mime", "commentMime"); |
||
111 | digester.addSetNext("meta/item", "addItem"); |
||
112 | |||
113 | return digester; |
||
114 | } |
||
115 | |||
116 | public void init(ResourceFactory resourceFactory, String configPath) |
||
117 | { |
||
118 | logger.info("starting"); |
||
119 | config = (ConfigRoot)configDigester.parse(resourceFactory.getAsStream(configPath)); |
||
120 | /* |
||
936 | dev | 121 | this.imagesRoot = new File(imagesRoot); |
122 | this.imagesFilter = new ImagesFilter(imagesMask); |
||
123 | if(columns != null) this.columns = columns.intValue(); |
||
1242 | dev | 124 | if(rows != null) this.rows = rows.intValue(); |
936 | dev | 125 | |
126 | this.thumbnailer = new Thumbnailer(); |
||
127 | this.thumbnailer.setImagesRoot(this.imagesRoot); |
||
128 | this.thumbnailer.setCacheDir(new File(cacheDir)); |
||
129 | |||
130 | if(thumbnailFormat != null) |
||
131 | this.thumbnailer.setFormat(thumbnailFormat); |
||
132 | if(smallWidth != null) |
||
133 | this.thumbnailer.setSmallWidth(smallWidth.intValue()); |
||
134 | if(smallHeight != null) |
||
135 | this.thumbnailer.setSmallHeight(smallHeight.intValue()); |
||
136 | if(mediumWidth != null) |
||
137 | this.thumbnailer.setMediumWidth(mediumWidth.intValue()); |
||
138 | if(mediumHeight != null) |
||
139 | this.thumbnailer.setMediumHeight(mediumHeight.intValue()); |
||
140 | |||
141 | thumbnailer.setResizer(new ak.photoalbum.images.jiu.JiuResizer()); |
||
142 | thumbnailer.setImagesFilter(this.imagesFilter); |
||
143 | thumbnailer.setDirTemplate(new File(dirTemplate)); |
||
144 | thumbnailer.setDirThumbnailPositions( |
||
145 | parseThumbnailPositions(dirThumbnailPositions)); |
||
146 | |||
147 | try { |
||
148 | thumbnailer.startup(); |
||
149 | } |
||
150 | catch(Exception ex) { |
||
151 | logger.error("init thumbnailer", ex); |
||
152 | } |
||
1247 | dev | 153 | */ |
936 | dev | 154 | logger.info("started"); |
155 | } |
||
156 | |||
157 | public void buildCache() |
||
158 | throws IOException |
||
159 | { |
||
160 | thumbnailer.buildCache(); |
||
161 | } |
||
162 | |||
163 | public void getEntry(String path, IndexEntry page, |
||
164 | IndexEntry index, IndexEntry prev, IndexEntry current, IndexEntry next) |
||
1242 | dev | 165 | throws IOException, SAXException, LogicException |
936 | dev | 166 | { |
167 | File file = new File(imagesRoot, path); |
||
168 | |||
169 | securePath(imagesRoot, file); |
||
170 | |||
171 | if(!file.exists()) |
||
172 | throw new FileNotFoundException( |
||
173 | "[" + file.getCanonicalPath() + "] not found"); |
||
174 | |||
175 | File dir = file.getParentFile(); |
||
176 | File[] children = dir.listFiles(imagesFilter); |
||
177 | int pos; |
||
178 | |||
179 | Arrays.sort(children, fileNameComparator); |
||
180 | pos = Arrays.binarySearch(children, file, fileNameComparator); |
||
181 | |||
182 | if(pos < 0) |
||
183 | throw new FileNotFoundException("[" + file.getCanonicalPath() |
||
184 | + "] not found in [" + dir.getCanonicalPath() + "]"); |
||
185 | |||
1242 | dev | 186 | metaInfos.clear(); // FIXME make this more intelligent |
936 | dev | 187 | setEntryInfo(page, file, false); |
188 | setEntryInfo(current, file, true); |
||
189 | setEntryInfo(index, dir, true); |
||
1242 | dev | 190 | if(pos > 0) setEntryInfo(prev, children[pos-1], true); |
191 | if(pos < children.length-1) setEntryInfo(next, children[pos+1], true); |
||
936 | dev | 192 | } |
193 | |||
194 | protected void setEntryInfo(IndexEntry entry, File file, boolean small) |
||
1242 | dev | 195 | throws IOException, SAXException |
936 | dev | 196 | { |
197 | String title = file.getName(); |
||
198 | int[] size; |
||
199 | String path = getPath(file); |
||
200 | |||
201 | if(file.isDirectory()) { |
||
202 | size = thumbnailer.getDirSize(file); |
||
203 | } |
||
204 | else { |
||
1242 | dev | 205 | title = FileUtils.extractFileName(title); |
936 | dev | 206 | |
207 | if(small) |
||
208 | size = thumbnailer.getSmallSize(file); |
||
1242 | dev | 209 | else |
936 | dev | 210 | size = thumbnailer.getMediumSize(file); |
211 | } |
||
212 | |||
213 | entry.setFile(file); |
||
214 | entry.setPath(path == null ? null : URLEncoder.encode(path, URL_ENCODING)); |
||
215 | entry.setTitle(title); |
||
216 | entry.setIsDir(file.isDirectory()); |
||
217 | entry.setWidth(size[0]); |
||
218 | entry.setHeight(size[1]); |
||
1242 | dev | 219 | |
220 | MetaInfoItem meta = findMetaInfo(imagesRoot, file); |
||
221 | if(meta != null) { |
||
222 | if(meta.getTitle() != null) { |
||
223 | entry.setTitle(meta.getTitle()); |
||
224 | } |
||
225 | if(meta.getSubtitle() != null) { |
||
226 | entry.setSubtitle(meta.getSubtitle()); |
||
227 | entry.setSubtitleMime(meta.getSubtitleMime()); |
||
228 | } |
||
229 | if(meta.getComment() != null) { |
||
230 | entry.setComment(meta.getComment()); |
||
231 | entry.setCommentMime(meta.getCommentMime()); |
||
232 | } |
||
233 | } |
||
936 | dev | 234 | } |
235 | |||
236 | public String getThumbnailMime() |
||
237 | { |
||
238 | return thumbnailer.getMime(); |
||
239 | } |
||
240 | |||
241 | public String getOriginMime(String path) |
||
242 | throws IOException, LogicException |
||
243 | { |
||
244 | File file = new File(imagesRoot, path); |
||
245 | |||
246 | if(!file.exists()) return null; |
||
247 | securePath(imagesRoot, file); |
||
248 | |||
249 | return FileUtils.getMime(FileUtils.extractFileExt(path)); |
||
250 | } |
||
251 | |||
252 | public void writeDir(String path, OutputStream out) |
||
253 | throws IOException, LogicException |
||
254 | { |
||
255 | File file = new File(imagesRoot, path); |
||
256 | |||
257 | securePath(imagesRoot, file); |
||
258 | thumbnailer.writeDir(file, out); |
||
259 | } |
||
260 | |||
261 | public void writeSmall(String path, OutputStream out) |
||
262 | throws IOException, LogicException |
||
263 | { |
||
264 | File file = new File(imagesRoot, path); |
||
265 | |||
266 | securePath(imagesRoot, file); |
||
267 | thumbnailer.writeSmall(file, out); |
||
268 | } |
||
269 | |||
270 | public void writeMedium(String path, OutputStream out) |
||
271 | throws IOException, LogicException |
||
272 | { |
||
273 | File file = new File(imagesRoot, path); |
||
274 | |||
275 | securePath(imagesRoot, file); |
||
276 | thumbnailer.writeMedium(file, out); |
||
277 | } |
||
278 | |||
279 | public void writeOrigin(String path, OutputStream out) |
||
280 | throws IOException, LogicException |
||
281 | { |
||
282 | FileInputStream in = null; |
||
283 | File file = new File(imagesRoot, path); |
||
284 | |||
285 | securePath(imagesRoot, file); |
||
286 | |||
287 | try { |
||
288 | in = new FileInputStream(file); |
||
289 | FileUtils.copyStreams(in, out); |
||
290 | } |
||
291 | finally { |
||
292 | if(in != null) in.close(); |
||
293 | } |
||
294 | } |
||
295 | |||
1242 | dev | 296 | protected MetaInfo getMetaInfo(File dir) |
297 | throws IOException, SAXException |
||
298 | { |
||
299 | MetaInfo meta = (MetaInfo)metaInfos.get(dir); |
||
300 | if(meta != null) return meta; |
||
301 | |||
302 | File metaFile = new File(dir, META_FILE_NAME); |
||
303 | if(!metaFile.exists()) return null; |
||
304 | |||
305 | meta = (MetaInfo)metaDigester.parse(new FileInputStream(metaFile)); |
||
306 | meta.setDir(dir); |
||
307 | metaInfos.put(dir, meta); |
||
308 | |||
309 | return meta; |
||
310 | } |
||
311 | |||
312 | protected MetaInfoItem findMetaInfo(File rootDir, File file) |
||
313 | throws IOException, SAXException |
||
314 | { |
||
315 | file = file.getCanonicalFile(); |
||
316 | rootDir = rootDir.getCanonicalFile(); |
||
317 | |||
318 | File dir = file; |
||
319 | if(!dir.isDirectory()) |
||
320 | dir = dir.getParentFile(); |
||
321 | |||
322 | MetaInfoItem metaItem = null; |
||
323 | for(; metaItem == null; dir = dir.getParentFile()) { |
||
324 | if(dir == null) break; |
||
325 | |||
326 | MetaInfo meta = getMetaInfo(dir); |
||
327 | if(meta != null) { |
||
328 | metaItem = meta.findItem(file); |
||
329 | } |
||
330 | if(rootDir.equals(dir)) break; |
||
331 | } |
||
332 | |||
333 | return metaItem; |
||
334 | } |
||
335 | |||
336 | public boolean listDirectory(String dirName, int page, List table, List pages) |
||
337 | throws IOException, LogicException, SAXException |
||
338 | { |
||
936 | dev | 339 | File dir = new File(imagesRoot, dirName); |
340 | |||
341 | securePath(imagesRoot, dir); |
||
1242 | dev | 342 | if(!dir.exists()) return false; |
936 | dev | 343 | |
1242 | dev | 344 | File[] children = dir.listFiles(imagesFilter); |
345 | int pos = page * columns * rows; |
||
936 | dev | 346 | |
347 | Arrays.sort(children, fileNameComparator); |
||
1242 | dev | 348 | metaInfos.clear(); // FIXME do this more intelligent (?) |
936 | dev | 349 | |
1242 | dev | 350 | // the pages list |
351 | pages.clear(); |
||
352 | for(int i = 0; i < (int)Math.ceil((double)children.length / columns / rows); i++) { |
||
353 | pages.add(new PageItem(i, i == page)); |
||
354 | } |
||
355 | |||
356 | // the main table |
||
357 | table.clear(); |
||
358 | while(pos < children.length && pos < (page+1) * columns * rows) { |
||
936 | dev | 359 | List row = new ArrayList(); |
360 | int rowPos = 0; |
||
361 | |||
1242 | dev | 362 | table.add(row); |
936 | dev | 363 | |
364 | while(rowPos < columns && pos < children.length) { |
||
365 | String path = getPath(children[pos]); |
||
366 | String title = children[pos].getName(); |
||
367 | int[] size; |
||
368 | |||
369 | if(children[pos].isDirectory()) { |
||
370 | size = thumbnailer.getDirSize(children[pos]); |
||
371 | } |
||
372 | else { |
||
373 | size = thumbnailer.getSmallSize(children[pos]); |
||
374 | title = FileUtils.extractFileName(title); |
||
375 | } |
||
376 | |||
1242 | dev | 377 | IndexEntry entry = new IndexEntry(children[pos], |
936 | dev | 378 | URLEncoder.encode(path, URL_ENCODING), |
1242 | dev | 379 | title, children[pos].isDirectory(), size[0], size[1]); |
380 | |||
381 | MetaInfoItem meta = findMetaInfo(imagesRoot, children[pos]); |
||
382 | if(meta != null) { |
||
383 | if(meta.getTitle() != null) { |
||
384 | entry.setTitle(meta.getTitle()); |
||
385 | } |
||
386 | if(meta.getSubtitle() != null) { |
||
387 | entry.setSubtitle(meta.getSubtitle()); |
||
388 | entry.setSubtitleMime(meta.getSubtitleMime()); |
||
389 | } |
||
390 | if(meta.getComment() != null) { |
||
391 | entry.setComment(meta.getComment()); |
||
392 | entry.setCommentMime(meta.getCommentMime()); |
||
393 | } |
||
394 | } |
||
395 | |||
396 | row.add(entry); |
||
936 | dev | 397 | rowPos++; |
398 | pos++; |
||
399 | } |
||
400 | |||
401 | while(rowPos < columns) { |
||
402 | row.add(null); |
||
403 | rowPos++; |
||
404 | } |
||
405 | } |
||
406 | |||
1242 | dev | 407 | return true; |
936 | dev | 408 | } |
409 | |||
410 | protected String getPath(File file) |
||
411 | throws IOException |
||
412 | { |
||
413 | String path = file.getCanonicalPath(); |
||
414 | String rootPath = imagesRoot.getCanonicalPath(); |
||
415 | |||
416 | if(path.equals(rootPath)) return ""; |
||
417 | if(!rootPath.endsWith(File.separator)) rootPath += File.separator; |
||
418 | |||
419 | if(!path.startsWith(rootPath)) |
||
420 | return null; |
||
421 | |||
422 | return path.substring(rootPath.length()); |
||
423 | } |
||
424 | |||
425 | protected ThumbnailPosition[] parseThumbnailPositions(String str) |
||
426 | { |
||
427 | List list = new ArrayList(); |
||
428 | StringTokenizer tokenizer = new StringTokenizer(str, ";"); |
||
429 | |||
430 | while(tokenizer.hasMoreTokens()) { |
||
431 | StringTokenizer tokenParser |
||
432 | = new StringTokenizer(tokenizer.nextToken(), ","); |
||
433 | int x = Integer.parseInt(tokenParser.nextToken().trim()); |
||
434 | int y = Integer.parseInt(tokenParser.nextToken().trim()); |
||
435 | int width = Integer.parseInt(tokenParser.nextToken().trim()); |
||
436 | int height = Integer.parseInt(tokenParser.nextToken().trim()); |
||
437 | String horAlignStr = tokenParser.nextToken().trim().toLowerCase(); |
||
438 | String vertAlignStr = tokenParser.nextToken().trim().toLowerCase(); |
||
439 | int horAlign; |
||
440 | int vertAlign; |
||
441 | |||
442 | if("l".equals(horAlignStr)) |
||
443 | horAlign = ThumbnailPosition.ALIGN_HOR_LEFT; |
||
444 | else if("r".equals(horAlignStr)) |
||
445 | horAlign = ThumbnailPosition.ALIGN_HOR_RIGHT; |
||
446 | else if("c".equals(horAlignStr)) |
||
447 | horAlign = ThumbnailPosition.ALIGN_HOR_CENTER; |
||
448 | else |
||
449 | throw new RuntimeException( |
||
450 | "Cannot parse " + horAlignStr + " as horizontal alignment"); |
||
451 | |||
452 | if("t".equals(vertAlignStr)) |
||
453 | vertAlign = ThumbnailPosition.ALIGN_VERT_TOP; |
||
454 | else if("b".equals(vertAlignStr)) |
||
455 | vertAlign = ThumbnailPosition.ALIGN_VERT_BOTTOM; |
||
456 | else if("c".equals(vertAlignStr)) |
||
457 | vertAlign = ThumbnailPosition.ALIGN_VERT_CENTER; |
||
458 | else |
||
459 | throw new RuntimeException( |
||
460 | "Cannot parse " + vertAlignStr + " as vertical alignment"); |
||
461 | |||
462 | list.add(new ThumbnailPosition(x, y, width, height, horAlign, vertAlign)); |
||
463 | } |
||
464 | |||
465 | ThumbnailPosition[] res = new ThumbnailPosition[list.size()]; |
||
466 | |||
467 | for(int i = 0; i < res.length; i++) |
||
468 | res[i] = (ThumbnailPosition)list.get(i); |
||
469 | |||
470 | return res; |
||
471 | } |
||
472 | |||
473 | protected static final Logic instance = new Logic(); |
||
474 | |||
475 | public static Logic getLogic() |
||
476 | { |
||
477 | return instance; |
||
478 | } |
||
479 | |||
480 | /** |
||
481 | * checks if given file is really under the parent directory |
||
482 | */ |
||
483 | protected void securePath(File parentDir, File file) |
||
484 | throws IOException, LogicException |
||
485 | { |
||
486 | if(parentDir == null || file == null) return; |
||
487 | |||
488 | File partFile = file.getCanonicalFile(); |
||
489 | |||
490 | parentDir = parentDir.getCanonicalFile(); |
||
491 | while(partFile != null) { |
||
492 | if(partFile.equals(parentDir)) return; |
||
493 | partFile = partFile.getParentFile(); |
||
494 | } |
||
495 | |||
496 | throw new LogicSecurityException( |
||
497 | "[" + file.getCanonicalPath() + "] is outside of directory [" |
||
498 | + parentDir.getCanonicalPath() + "]"); |
||
499 | } |
||
500 | } |