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