From 0a7867cf14fd24f738a641219f4a51c8304d9251 Mon Sep 17 00:00:00 2001 From: Erik Forkalsrud Date: Sun, 3 May 2009 17:38:35 -0700 Subject: [PATCH] better sorting of directories --- .../album/exif/ComparatorFactory.java | 4 +- .../forkalsrud/album/exif/DirectoryEntry.java | 405 ++++++++++-------- src/org/forkalsrud/album/exif/Entry.java | 4 + .../forkalsrud/album/web/AlbumServlet.java | 11 +- 4 files changed, 233 insertions(+), 191 deletions(-) diff --git a/src/org/forkalsrud/album/exif/ComparatorFactory.java b/src/org/forkalsrud/album/exif/ComparatorFactory.java index ae90621..a9c4db3 100644 --- a/src/org/forkalsrud/album/exif/ComparatorFactory.java +++ b/src/org/forkalsrud/album/exif/ComparatorFactory.java @@ -44,8 +44,8 @@ public class ComparatorFactory { public int compare(Entry e1, Entry e2) { - Date d1 = e1.getDate(); - Date d2 = e2.getDate(); + Date d1 = e1.getEarliest(); + Date d2 = e2.getEarliest(); if (d1 != null && d2 != null) { return d1.compareTo(d2); } else if (d1 != null) { diff --git a/src/org/forkalsrud/album/exif/DirectoryEntry.java b/src/org/forkalsrud/album/exif/DirectoryEntry.java index 978eca0..662466b 100644 --- a/src/org/forkalsrud/album/exif/DirectoryEntry.java +++ b/src/org/forkalsrud/album/exif/DirectoryEntry.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Properties; import javax.imageio.ImageIO; @@ -43,223 +44,230 @@ public class DirectoryEntry extends Entry { final static String OVERRIDE_FILE = "album.properties"; File cache; - List children = new ArrayList(); - boolean childrenLoaded = false; - Comparator sort = null; + List children = new ArrayList(); + boolean childrenLoaded = false; + Comparator sort = null; + Date earliest = null; - public DirectoryEntry(File file) { - super(file); - if (!file.isDirectory()) { - throw new RuntimeException("Use DirectoryEntry only for directories: " + file); - } - cache = new File(file, CACHE_FILE); + public DirectoryEntry(File file) { + super(file); + if (!file.isDirectory()) { + throw new RuntimeException("Use DirectoryEntry only for directories: " + file); + } + cache = new File(file, CACHE_FILE); } - public DirectoryEntry(Entry parent, File file) { - this(file); - this.parent = parent; - } + public DirectoryEntry(Entry parent, File file) { + this(file); + this.parent = parent; + } - public static Entry getEntry(File f) { - return new DirectoryEntry(f.getParentFile()).get(f); - } + public static Entry getEntry(File f) { + return new DirectoryEntry(f.getParentFile()).get(f); + } - @Override - public boolean isFile() { - return false; - } + @Override + public boolean isFile() { + return false; + } - public List getContents() { + public List getContents() { - loadChildren(); - return children; - } + loadChildren(); + return children; + } - @Override - public Thumbnail getThumbnail() { - loadChildren(); - return super.getThumbnail(); - } + @Override + public Thumbnail getThumbnail() { + loadChildren(); + return super.getThumbnail(); + } - @Override - public String getCaption() { - loadChildren(); - return super.getCaption(); - } + @Override + public String getCaption() { + loadChildren(); + return super.getCaption(); + } - public void addContents(Entry entry) { - children.add(entry); - } + public void addContents(Entry entry) { + children.add(entry); + } - public Entry get(File f) { - loadChildren(); - for (Entry e : children) { - if (f.equals(e.file)) { - return e; - } - } - return null; - } + public Entry get(File f) { + loadChildren(); + for (Entry e : children) { + if (f.equals(e.file)) { + return e; + } + } + return null; + } - protected void loadChildren() { + protected void loadChildren() { - if (childrenLoaded) { - return; - } - try { - Properties cachedProps = isCacheCurrent() ? loadFromFile(cache) : generateCache(); - Properties combined = new Properties(); - combined.putAll(cachedProps); - File override = new File(file, OVERRIDE_FILE); - if (override.exists() && override.isFile() && override.canRead()) { - Properties overrideProps = loadFromFile(override); - combined.putAll(overrideProps); - } - populate(combined); - sort(); - fillLinkedList(); - childrenLoaded = true; - } catch (Exception e) { - throw new RuntimeException(e); - } - } + if (childrenLoaded) { + return; + } + try { + Properties cachedProps = isCacheCurrent() ? loadFromFile(cache) : generateCache(); + Properties combined = new Properties(); + combined.putAll(cachedProps); + File override = new File(file, OVERRIDE_FILE); + if (override.exists() && override.isFile() && override.canRead()) { + Properties overrideProps = loadFromFile(override); + combined.putAll(overrideProps); + } + populate(combined); + sort(); + fillLinkedList(); + childrenLoaded = true; + } catch (Exception e) { + throw new RuntimeException(e); + } + } - boolean isCacheCurrent() { + boolean isCacheCurrent() { return cache.exists() && cache.isFile() && cache.canRead() && file.lastModified() <= cache.lastModified(); - } + } - Properties loadFromFile(File propFile) throws IOException { + Properties loadFromFile(File propFile) throws IOException { - Properties props = new Properties(); - props.load(new FileInputStream(propFile)); - return props; - } + Properties props = new Properties(); + props.load(new FileInputStream(propFile)); + return props; + } - Properties generateCache() throws MetadataException, JpegProcessingException, IOException { + Properties generateCache() throws MetadataException, JpegProcessingException, IOException, ParseException { - long start = System.currentTimeMillis(); - Properties props = new Properties(); - generateFileEntries(props); - props.store(new FileOutputStream(cache), "Extra Comments"); - long end = System.currentTimeMillis(); - System.out.println("Time to generate properties for " + file.getPath() + ": " + (end - start)/1000d + " s"); + long start = System.currentTimeMillis(); + Properties props = new Properties(); + generateFileEntries(props); + props.store(new FileOutputStream(cache), "Extra Comments"); + long end = System.currentTimeMillis(); + System.out.println("Time to generate properties for " + file.getPath() + ": " + (end - start)/1000d + " s"); - return props; - } + return props; + } - void generateFileEntries(Properties props) - throws IOException, MetadataException, JpegProcessingException { + void generateFileEntries(Properties props) + throws IOException, MetadataException, JpegProcessingException, ParseException { - File[] files = file.listFiles(); - for (File f : files) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss"); + File[] files = file.listFiles(); + for (File f : files) { - String name = f.getName(); - if (f.isDirectory()) { - if ("CVS".equals(name)) { - continue; - } - if (f.isHidden()) { - continue; - } - generateDirectoryProperties(props, f); - continue; - } - if (file.isHidden()) { - continue; - } - if (CACHE_FILE.equals(name) || OVERRIDE_FILE.equals(name)) { - continue; - } - if (name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".JPG")) { - generateThumbnailProperties(props, f); - } - } - } + String name = f.getName(); + if (f.isDirectory()) { + if ("CVS".equals(name)) { + continue; + } + if (f.isHidden()) { + continue; + } + generateDirectoryProperties(props, f); + continue; + } + if (file.isHidden()) { + continue; + } + if (CACHE_FILE.equals(name) || OVERRIDE_FILE.equals(name)) { + continue; + } + if (name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".JPG")) { + String base = "file." + f.getName() + "."; + Map p = generateThumbnailProperties(f); + for (Map.Entry e : p.entrySet()) { + props.setProperty(base + e.getKey(), e.getValue()); + } + } + } + } - private void generateDirectoryProperties(Properties props, File f) { + private void generateDirectoryProperties(Properties props, File f) { - String name = f.getName(); - props.setProperty("dir." + name, "present"); - } + String name = f.getName(); + props.setProperty("dir." + name, "present"); + } - private void generateThumbnailProperties(Properties props, File f) - throws JpegProcessingException, MetadataException, IOException { - String name = f.getName(); - String base = "file." + name + "."; - boolean hasDate = false; - boolean hasOrientation = false; - boolean hasDim = false; - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss"); - NumberFormat nf = new DecimalFormat("0"); + private Map generateThumbnailProperties(File f) + throws JpegProcessingException, MetadataException, IOException { + HashMap props = new HashMap(); + String name = f.getName(); + boolean hasDate = false; + boolean hasOrientation = false; + boolean hasDim = false; + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss"); + NumberFormat nf = new DecimalFormat("0"); - Metadata metadata; - try { - metadata = JpegMetadataReader.readMetadata(f); - Directory exifDirectory = metadata.getDirectory(ExifDirectory.class); - if (exifDirectory.containsTag(ExifDirectory.TAG_ORIENTATION)) { - int orientation = exifDirectory.getInt(ExifDirectory.TAG_ORIENTATION); - props.setProperty(base + "orientation", nf.format(orientation)); - hasOrientation = true; - } - if (exifDirectory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_WIDTH) && - exifDirectory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT)) { - int width = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_WIDTH); - int height = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT); - props.setProperty(base + "dimensions", new Dimension(width, height).toString()); - hasDim = true; - } - if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) { - Date captureDate = getExifDate(exifDirectory, ExifDirectory.TAG_DATETIME_ORIGINAL); - if (captureDate != null) { - props.setProperty(base + "captureDate", sdf.format(captureDate)); - hasDate = true; - } - } - if (exifDirectory.containsTag(ExifDirectory.TAG_USER_COMMENT)) { - String comment = exifDirectory.getString(ExifDirectory.TAG_USER_COMMENT); - props.setProperty(base + "comment", comment); - } - Directory jpegDirectory = metadata.getDirectory(JpegDirectory.class); - if (jpegDirectory.containsTag(JpegDirectory.TAG_JPEG_IMAGE_WIDTH) && - jpegDirectory.containsTag(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT)) { - int width = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_WIDTH); - int height = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT); - props.setProperty(base + "dimensions", new Dimension(width, height).toString()); - hasDim = true; - } + Metadata metadata; + try { + metadata = JpegMetadataReader.readMetadata(f); + Directory exifDirectory = metadata.getDirectory(ExifDirectory.class); + if (exifDirectory.containsTag(ExifDirectory.TAG_ORIENTATION)) { + int orientation = exifDirectory.getInt(ExifDirectory.TAG_ORIENTATION); + props.put("orientation", nf.format(orientation)); + hasOrientation = true; + } + if (exifDirectory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_WIDTH) && + exifDirectory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT)) { + int width = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_WIDTH); + int height = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT); + props.put("dimensions", new Dimension(width, height).toString()); + hasDim = true; + } + if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) { + Date captureDate = getExifDate(exifDirectory, ExifDirectory.TAG_DATETIME_ORIGINAL); + if (captureDate != null) { + props.put("captureDate", sdf.format(captureDate)); + hasDate = true; + } + } + if (exifDirectory.containsTag(ExifDirectory.TAG_USER_COMMENT)) { + String comment = exifDirectory.getString(ExifDirectory.TAG_USER_COMMENT); + props.put("comment", comment); + } + Directory jpegDirectory = metadata.getDirectory(JpegDirectory.class); + if (jpegDirectory.containsTag(JpegDirectory.TAG_JPEG_IMAGE_WIDTH) && + jpegDirectory.containsTag(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT)) { + int width = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_WIDTH); + int height = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT); + props.put("dimensions", new Dimension(width, height).toString()); + hasDim = true; + } } catch (Exception e) { throw new RuntimeException("problem reading file " + f.getPath(), e); } - props.setProperty(base + "etag", Integer.toHexString(name.hashCode() + Long.valueOf(f.lastModified()).hashCode())); - if (!hasDate) { - props.setProperty(base + "captureDate", sdf.format(new Date(f.lastModified()))); - } - if (!hasOrientation) { - props.setProperty(base + "orientation", "1"); - } - if (!hasDim) { - Dimension dim = decodeImageForDimensions(f); - if (dim != null) { - props.setProperty(base + "dimensions", dim.toString()); - hasDim = true; - } - } - } + props.put("etag", Integer.toHexString(name.hashCode() + Long.valueOf(f.lastModified()).hashCode())); + if (!hasDate) { + props.put("captureDate", sdf.format(new Date(f.lastModified()))); + } + if (!hasOrientation) { + props.put("orientation", "1"); + } + if (!hasDim) { + Dimension dim = decodeImageForDimensions(f); + if (dim != null) { + props.put("dimensions", dim.toString()); + hasDim = true; + } + } + return props; + } - private Date getExifDate(Directory exifDirectory, int tagName) throws MetadataException { + private Date getExifDate(Directory exifDirectory, int tagName) throws MetadataException { - String dateStr = (String)exifDirectory.getObject(tagName); - if (" : : : : ".equals(dateStr)) { - return null; - } - return exifDirectory.getDate(tagName); - } + String dateStr = (String)exifDirectory.getObject(tagName); + if (" : : : : ".equals(dateStr)) { + return null; + } + return exifDirectory.getDate(tagName); + } - Dimension decodeImageForDimensions(File file) throws IOException { + Dimension decodeImageForDimensions(File file) throws IOException { String suffix = null; String name = file.getName(); @@ -278,10 +286,10 @@ public class DirectoryEntry extends Entry { return new Dimension(img.getWidth(), img.getHeight()); } - private void sort() { + private void sort() { - Collections.sort(children, sort); - } + Collections.sort(children, sort); + } void fillLinkedList() { @@ -305,19 +313,27 @@ public class DirectoryEntry extends Entry { String caption = props.getProperty("caption"); setCaption(caption); sort = ComparatorFactory.getSort(props.getProperty("sort")); + HashMap entryMap = new HashMap(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss"); - Iterator i = props.keySet().iterator(); + + Date oldest = new Date(file.lastModified()); + + Iterator i = props.keySet().iterator(); while (i.hasNext()) { String key = (String)i.next(); if (key.startsWith("file.") && key.endsWith(".dimensions")) { String name = key.substring("file.".length(), key.length() - ".dimensions".length()); if (!entryMap.containsKey(name)) { - File f = new File(file, name); + File f = new File(file, name); FileEntry entry = new FileEntry(this, f); Thumbnail thumbnail = new Thumbnail(f); entry.setCaption(props.getProperty("file." + name + ".caption")); - entry.setDate(sdf.parse(props.getProperty("file." + name + ".captureDate"))); + Date fileDate = sdf.parse(props.getProperty("file." + name + ".captureDate")); + if (fileDate.before(oldest)) { + oldest = fileDate; + } + entry.setDate(fileDate); thumbnail.setSize(new Dimension(props.getProperty("file." + name + ".dimensions"))); thumbnail.setOrientation(Integer.parseInt(props.getProperty("file." + name + ".orientation"))); thumbnail.setEtag(props.getProperty("file." + name + ".etag")); @@ -331,16 +347,29 @@ public class DirectoryEntry extends Entry { } } } else if (key.startsWith("dir.") && !key.endsWith(".hidden")) { - String name = key.substring("dir.".length()); + String name = key.substring("dir.".length()); boolean hidden = Boolean.parseBoolean(props.getProperty("dir." + name + ".hidden")); if (!hidden) { - children.add(new DirectoryEntry(this, new File(file, name))); + DirectoryEntry dir = new DirectoryEntry(this, new File(file, name)); + children.add(dir); + Date fileDate = dir.getEarliest(); + if (fileDate != null && fileDate.before(oldest)) { + oldest = fileDate; + } } } } + this.earliest = oldest; if (thumbnail == null && !children.isEmpty()) { setThumbnail(children.get(0).getThumbnail()); } } + + + @Override + public Date getEarliest() { + loadChildren(); + return earliest; + } } diff --git a/src/org/forkalsrud/album/exif/Entry.java b/src/org/forkalsrud/album/exif/Entry.java index 148a83c..c076e64 100644 --- a/src/org/forkalsrud/album/exif/Entry.java +++ b/src/org/forkalsrud/album/exif/Entry.java @@ -100,4 +100,8 @@ public abstract class Entry { public File getPath() { return file; } + + public Date getEarliest() { + return getDate(); + } } diff --git a/src/org/forkalsrud/album/web/AlbumServlet.java b/src/org/forkalsrud/album/web/AlbumServlet.java index f1d16cf..afb6f1c 100644 --- a/src/org/forkalsrud/album/web/AlbumServlet.java +++ b/src/org/forkalsrud/album/web/AlbumServlet.java @@ -2,6 +2,8 @@ package org.forkalsrud.album.web; import java.io.File; import java.io.IOException; +import java.util.Calendar; +import java.util.Date; import java.util.concurrent.TimeoutException; import javax.servlet.RequestDispatcher; @@ -230,7 +232,6 @@ public class AlbumServlet public class Mapper { public String map(File file) { - StringBuilder buf = new StringBuilder(); return appendFile(buf, file).toString(); } @@ -245,7 +246,15 @@ public class AlbumServlet return appendFile(buf, file.getParentFile()).append('/').append(file.getName()); } } + + Calendar cal = Calendar.getInstance(); + + public String year(Date d) { + cal.setTime(d); + return String.valueOf(cal.get(Calendar.YEAR)); + } } + } // eof