comparison servlet/src/digilib/io/DocuDirectory.java @ 531:9cedd170b581 digilibPDF

* PDF generation works now even with subdirectories * genericsification and clean up
author robcast
date Fri, 05 Feb 2010 20:58:38 +0100
parents
children e758a49258e8
comparison
equal deleted inserted replaced
530:bd6569a95a3c 531:9cedd170b581
1 /* DocuDirectory -- Directory of DocuFilesets.
2
3 Digital Image Library servlet components
4
5 Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
6
7 This program is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the
9 Free Software Foundation; either version 2 of the License, or (at your
10 option) any later version.
11
12 Please read license.txt for the full details. A copy of the GPL
13 may be found at http://www.gnu.org/copyleft/lgpl.html
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19 * Created on 25.02.2003
20 */
21
22 package digilib.io;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.xml.sax.SAXException;
32
33 /**
34 * @author casties
35 */
36 public class DocuDirectory extends Directory {
37
38 /** list of files (DocuDirent) */
39 private List<List<DocuDirent>> list = null;
40
41 /** directory object is valid (exists on disk) */
42 private boolean isValid = false;
43
44 /** reference of the parent DocuDirCache */
45 private DocuDirCache cache = null;
46
47 /** directory name (digilib canonical form) */
48 private String dirName = null;
49
50 /** directory metadata */
51 private MetadataMap dirMeta = null;
52
53 /** state of metadata is valid */
54 private boolean metaChecked = false;
55
56 /** unresolved file metadata */
57 private Map<String, MetadataMap> unresolvedFileMeta = null;
58
59 /** time of last access of this object (not the filesystem) */
60 private long objectATime = 0;
61
62 /** time directory was last modified on the file system */
63 private long dirMTime = 0;
64
65 /**
66 * Constructor with digilib directory path and a parent DocuDirCache.
67 *
68 * Directory names at the given path are appended to the base directories
69 * from the cache. The directory is checked on disk and isValid is set.
70 *
71 * @see readDir
72 *
73 * @param path
74 * digilib directory path name
75 * @param cache
76 * parent DocuDirCache
77 */
78 public DocuDirectory(String path, DocuDirCache cache) {
79 this.dirName = path;
80 this.cache = cache;
81 initDir();
82 checkDir();
83 }
84
85 /**
86 * Sets and checks the dir object.
87 *
88 */
89 protected void initDir() {
90 String baseDirName = cache.getBaseDirNames()[0];
91 // clear directory first
92 //list = new ArrayList[FileOps.NUM_CLASSES];
93
94 list = new ArrayList<List<DocuDirent>>(FileOps.NUM_CLASSES);
95 // create empty list
96 for (int i=0; i < FileOps.NUM_CLASSES; ++i) {
97 list.add(null);
98 }
99 isValid = false;
100 dirMTime = 0;
101 // the first directory has to exist
102 dir = new File(baseDirName, dirName);
103 }
104
105 /**
106 * number of DocuFiles in this directory.
107 *
108 */
109 public int size() {
110 return ((list != null) && (list.get(0) != null)) ? list.get(0).size() : 0;
111 }
112
113 /**
114 * number of files of this class in this directory.
115 *
116 * @param fc
117 * fileClass
118 */
119 public int size(int fc) {
120 return ((list != null) && (list.get(fc) != null)) ? list.get(fc).size() : 0;
121 }
122
123 /**
124 * Returns the ImageFile at the index.
125 *
126 * @param index
127 * @return
128 */
129 public ImageFileset get(int index) {
130 if ((list == null) || (list.get(0) == null) || (index >= list.get(0).size())) {
131 return null;
132 }
133 return (ImageFileset) list.get(0).get(index);
134 }
135
136 /**
137 * Returns the file of the class at the index.
138 *
139 * @param index
140 * @param fc
141 * fileClass
142 * @return
143 */
144 public DocuDirent get(int index, int fc) {
145 if ((list == null) || (list.get(fc) == null) || (index >= list.get(fc).size())) {
146 return null;
147 }
148 return (DocuDirent) list.get(fc).get(index);
149 }
150
151 /**
152 * Checks if the directory exists on the filesystem.
153 *
154 * Sets isValid.
155 *
156 * @return
157 */
158 public boolean checkDir() {
159 if (dir == null) {
160 initDir();
161 }
162 isValid = dir.isDirectory();
163 return isValid;
164 }
165
166 /**
167 * Read the filesystem directory and fill this object.
168 *
169 * Clears the List and (re)reads all files.
170 *
171 * @return boolean the directory exists
172 */
173 public synchronized boolean readDir() {
174 // check directory first
175 checkDir();
176 if (!isValid) {
177 return false;
178 }
179 // first file extension to try for scaled directories
180 String scalext = null;
181 // read all filenames
182 logger.debug("reading directory " + dir.getPath());
183 /*
184 * using ReadableFileFilter is safer (we won't get directories with file
185 * extensions) but slower.
186 */
187 File[] allFiles = null;
188 if (cache.safeDirIndex) {
189 allFiles = dir.listFiles(new FileOps.ReadableFileFilter());
190 } else {
191 allFiles = dir.listFiles();
192 }
193 //logger.debug(" done");
194 if (allFiles == null) {
195 // not a directory
196 return false;
197 }
198 // list of base dirs from the parent cache
199 String[] baseDirNames = cache.getBaseDirNames();
200 // number of base dirs
201 int nb = baseDirNames.length;
202 // array of base dirs
203 Directory[] dirs = new Directory[nb];
204 // first entry is this directory
205 dirs[0] = this;
206 // fill array with the remaining directories
207 for (int j = 1; j < nb; j++) {
208 File d = new File(baseDirNames[j], dirName);
209 if (d.isDirectory()) {
210 dirs[j] = new Directory(d);
211 logger.debug(" reading scaled directory " + d.getPath());
212 dirs[j].readDir();
213 //logger.debug(" done");
214 }
215 }
216
217 // go through all file classes
218 for (int classIdx = 0; classIdx < FileOps.NUM_CLASSES; classIdx++) {
219 int fileClass = cache.getFileClasses()[classIdx];
220 //logger.debug("filtering directory "+dir.getPath()+" for class
221 // "+fc);
222 File[] fileList = FileOps.listFiles(allFiles, FileOps
223 .filterForClass(fileClass));
224 //logger.debug(" done");
225 // number of files in the directory
226 int numFiles = fileList.length;
227 if (numFiles > 0) {
228 // create new list
229 list.set(fileClass, new ArrayList<DocuDirent>(numFiles));
230 // sort the file names alphabetically and iterate the list
231 // Arrays.sort(fileList); // not needed <hertzhaft>
232 Map<Integer, Object> hints = FileOps.newHints(FileOps.HINT_BASEDIRS, dirs);
233 hints.put(FileOps.HINT_FILEEXT, scalext);
234 for (int i = 0; i < numFiles; i++) {
235 DocuDirent f = FileOps.fileForClass(fileClass, fileList[i],
236 hints);
237 // add the file to our list
238 // logger.debug(f.getName());
239
240 list.get(fileClass).add(f);
241 f.setParent(this);
242 }
243 // we sort the inner ArrayList (the list of files not the list of file types)
244 // for binarySearch to work (DocuDirent's natural sort order is by filename)
245 Collections.sort(list.get(fileClass));
246 }
247 }
248 // clear the scaled directories
249 for (int j = 1; j < nb; j++) {
250 if (dirs[j] != null) {
251 dirs[j].clearFilenames();
252 }
253 }
254 // update number of cached files if this was the first time
255 if (dirMTime == 0) {
256 cache.numFiles += size();
257 }
258 dirMTime = dir.lastModified();
259 // read metadata as well
260 readMeta();
261 return isValid;
262 }
263
264 /**
265 * Check to see if the directory has been modified and reread if necessary.
266 *
267 * @return boolean the directory is valid
268 */
269 public boolean refresh() {
270 if (isValid) {
271 if (dir.lastModified() > dirMTime) {
272 // on-disk modification time is more recent
273 readDir();
274 }
275 touch();
276 }
277 return isValid;
278 }
279
280 /**
281 * Read directory metadata.
282 *
283 */
284 public void readMeta() {
285 // check for directory metadata...
286 File mf = new File(dir, "index.meta");
287 if (mf.canRead()) {
288 XMLMetaLoader ml = new XMLMetaLoader();
289 try {
290 // read directory meta file
291 Map<String, MetadataMap> fileMeta = ml.loadURL(mf.getAbsolutePath());
292 if (fileMeta == null) {
293 return;
294 }
295 // meta for the directory itself is in the "" bin
296 dirMeta = fileMeta.remove("");
297 // read meta for files in this directory
298 readFileMeta(fileMeta, null);
299 // is there meta for other files left?
300 if (fileMeta.size() > 0) {
301 unresolvedFileMeta = fileMeta;
302 }
303 } catch (SAXException e) {
304 logger.warn("error parsing index.meta", e);
305 } catch (IOException e) {
306 logger.warn("error reading index.meta", e);
307 }
308 }
309 readParentMeta();
310 metaChecked = true;
311 }
312
313 /**
314 * Read metadata from all known parent directories.
315 *
316 */
317 public void readParentMeta() {
318 // check the parent directories for additional file meta
319 Directory dd = parent;
320 String path = dir.getName();
321 while (dd != null) {
322 if (((DocuDirectory) dd).hasUnresolvedFileMeta()) {
323 readFileMeta(((DocuDirectory) dd).unresolvedFileMeta, path);
324 }
325 // prepend parent dir path
326 path = dd.dir.getName() + "/" + path;
327 // become next parent
328 dd = dd.parent;
329 }
330 }
331
332 /**
333 * Read metadata for the files in this directory.
334 *
335 * Takes a Map with meta-information, adding the relative path before the
336 * lookup.
337 *
338 * @param fileMeta
339 * @param relPath
340 * @param fc
341 * fileClass
342 */
343 protected void readFileMeta(Map<String,MetadataMap> fileMeta, String relPath) {
344 if (list == null) {
345 // there are no files
346 return;
347 }
348 String path = (relPath != null) ? (relPath + "/") : "";
349 // go through all file classes
350 for (int nc = 0; nc < list.size(); nc++) {
351 int fc = cache.getFileClasses()[nc];
352 if (list.get(fc) == null) {
353 continue;
354 }
355 // iterate through the list of files in this directory
356 for (DocuDirent f: list.get(fc)) {
357 // prepend path to the filename
358 String fn = path + f.getName();
359 // look up meta for this file and remove from dir
360 MetadataMap meta = fileMeta.remove(fn);
361 if (meta != null) {
362 // store meta in file
363 f.setFileMeta(meta);
364 }
365 }
366 }
367 }
368
369 protected void notifyChildMeta(MetadataMap childmeta) {
370 List<DocuDirectory> children = cache.getChildren(this.dirName, true);
371 if (children.size() > 0) {
372 /*for (DocuDirectory d: children) {
373 // TODO: finish this!
374 //((DocuDirectory) i.next()).readFileMeta()
375 }*/
376 }
377 }
378
379 /**
380 * Update access time.
381 *
382 * @return long time of last access.
383 */
384 public long touch() {
385 long t = objectATime;
386 objectATime = System.currentTimeMillis();
387 return t;
388 }
389
390 /**
391 * Searches for the file with the name <code>fn</code>.
392 *
393 * Searches the directory for the file with the name <code>fn</code> and
394 * returns its index. Returns -1 if the file cannot be found.
395 *
396 * @param fn
397 * filename
398 * @param fc
399 * file class
400 * @return int index of file <code>fn</code>
401 */
402 public int indexOf(String fn) {
403 int fc = FileOps.classForFilename(fn);
404 return indexOf(fn, fc);
405 }
406
407 /**
408 * Searches for the file with the name <code>fn</code> and class fc.
409 *
410 * Searches the directory for the file with the name <code>fn</code> and
411 * returns its index. Returns -1 if the file cannot be found.
412 *
413 * @param fn
414 * filename
415 * @return int index of file <code>fn</code>
416 */
417 public int indexOf(String fn, int fc) {
418 if (!isRead()) {
419 // read directory now
420 if (!readDir()) {
421 return -1;
422 }
423 }
424 List<DocuDirent> fileList = list.get(fc);
425 // empty directory?
426 if (fileList == null) {
427 return -1;
428 }
429
430 // search for exact match (DocuDirent does compareTo<String>)
431 // OBS: fileList needs to be sorted first (see )! <hertzhaft>
432 int idx = Collections.binarySearch(fileList, fn);
433 if (idx >= 0) {
434 return idx;
435 } else {
436 logger.debug(fn + " not found by binarysearch");
437 // try closest matches without extension
438 idx = -idx - 1;
439 if ((idx < fileList.size())
440 && isBaseInList(fileList, idx, fn)) {
441 // idx matches
442 return idx;
443 } else if ((idx > 0)
444 && isBaseInList(fileList, idx-1, fn)) {
445 // idx-1 matches
446 return idx - 1;
447 } else if ((idx + 1 < fileList.size())
448 && isBaseInList(fileList, idx+1, fn)) {
449 // idx+1 matches
450 return idx + 1;
451 }
452
453 }
454 return -1;
455 }
456
457 private boolean isBaseInList(List<DocuDirent> fl, int idx, String fn) {
458 String dfn = FileOps.basename((fl.get(idx))
459 .getName());
460 return (dfn.equals(fn)||dfn.equals(FileOps.basename(fn)));
461 }
462
463
464 /**
465 * Finds the DocuDirent with the name <code>fn</code>.
466 *
467 * Searches the directory for the DocuDirent with the name <code>fn</code>
468 * and returns it. Returns null if the file cannot be found.
469 *
470 * @param fn
471 * filename
472 * @return DocuDirent
473 */
474 public DocuDirent find(String fn) {
475 int fc = FileOps.classForFilename(fn);
476 int i = indexOf(fn, fc);
477 if (i >= 0) {
478 return (DocuDirent) list.get(0).get(i);
479 }
480 return null;
481 }
482
483 /**
484 * Finds the DocuDirent with the name <code>fn</code> and class
485 * <code>fc</code>.
486 *
487 * Searches the directory for the DocuDirent with the name <code>fn</code>
488 * and returns it. Returns null if the file cannot be found.
489 *
490 * @param fn
491 * filename
492 * @return DocuDirent
493 */
494 public DocuDirent find(String fn, int fc) {
495 int i = indexOf(fn, fc);
496 if (i >= 0) {
497 return (DocuDirent) list.get(fc).get(i);
498 }
499 return null;
500 }
501
502 /**
503 * Returns the digilib canonical name.
504 *
505 * @return
506 */
507 public String getDirName() {
508 return dirName;
509 }
510
511 /**
512 * The directory is valid (exists on disk).
513 *
514 * @return boolean
515 */
516 public boolean isValid() {
517 return isValid;
518 }
519
520 /**
521 * The directory has been read from disk.
522 *
523 * @return
524 */
525 public boolean isRead() {
526 return (dirMTime != 0);
527 }
528
529 /**
530 * @return long
531 */
532 public long getAccessTime() {
533 return objectATime;
534 }
535
536 /**
537 * @return Hashtable
538 */
539 public MetadataMap getDirMeta() {
540 return dirMeta;
541 }
542
543 /**
544 * Checks metadata
545 *
546 */
547 public void checkMeta() {
548 if (metaChecked) {
549 return;
550 } else {
551 readMeta();
552 }
553 }
554
555 /**
556 * @return long
557 */
558 public long getDirMTime() {
559 return dirMTime;
560 }
561
562 /**
563 * Sets the dirMeta.
564 *
565 * @param dirMeta
566 * The dirMeta to set
567 */
568 public void setDirMeta(MetadataMap dirMeta) {
569 this.dirMeta = dirMeta;
570 }
571
572 public boolean hasUnresolvedFileMeta() {
573 return (this.unresolvedFileMeta != null);
574 }
575
576 }