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