diff --git a/.classpath b/.classpath
index 4157214..f91a5c5 100644
--- a/.classpath
+++ b/.classpath
@@ -8,21 +8,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/org/forkalsrud/album/exif/Album.java b/src/org/forkalsrud/album/exif/Album.java
deleted file mode 100644
index e004243..0000000
--- a/src/org/forkalsrud/album/exif/Album.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- *
- */
-package org.forkalsrud.album.exif;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.List;
-
-/**
- * @author knut
- *
- */
-public class Album {
-
- Entry cover;
-
- List contents = new ArrayList();
-
- public Entry getCover() {
- return cover;
- }
-
-
- public List getContents() {
- return contents;
- }
-
-
- public void setCover(Entry cover) {
- this.cover = cover;
- }
-
-
- public void addContents(Entry entry) {
- contents.add(entry);
- }
-
-
-
- public void sort() {
-
- Collections.sort(contents, new Comparator() {
-
- public int compare(Entry e1, Entry e2) {
-
- if (!e1.isFile() && e2.isFile()) {
- return -1;
- } else if (e1.isFile() && !e2.isFile()) {
- return +1;
- }
- Date d1 = e1.getDate();
- Date d2 = e2.getDate();
- if (d1 != null && d2 != null) {
- return d1.compareTo(d2);
- } else if (d1 != null) {
- return -1;
- } else if (d2 != null) {
- return +1;
- } else {
- return 0;
- }
- }
- });
- fillLinkedList();
- }
-
- void fillLinkedList() {
-
- Entry prev = null;
- for (Entry e : contents) {
- e.prev = prev;
- if (prev != null) {
- prev.next = e;
- }
- prev = e;
- }
- if (prev != null) {
- prev.next = null;
- }
- }
-}
diff --git a/src/org/forkalsrud/album/exif/DirectoryEntry.java b/src/org/forkalsrud/album/exif/DirectoryEntry.java
new file mode 100644
index 0000000..b040732
--- /dev/null
+++ b/src/org/forkalsrud/album/exif/DirectoryEntry.java
@@ -0,0 +1,331 @@
+/**
+ *
+ */
+package org.forkalsrud.album.exif;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+
+import com.drew.imaging.jpeg.JpegMetadataReader;
+import com.drew.imaging.jpeg.JpegProcessingException;
+import com.drew.metadata.Directory;
+import com.drew.metadata.Metadata;
+import com.drew.metadata.MetadataException;
+import com.drew.metadata.exif.ExifDirectory;
+import com.drew.metadata.jpeg.JpegDirectory;
+
+/**
+ * @author knut
+ */
+public class DirectoryEntry extends Entry {
+
+ final static String CACHE_FILE = "cache.properties";
+ final static String OVERRIDE_FILE = "album.properties";
+
+ File cache;
+ List children = new ArrayList();
+ boolean childrenLoaded = false;
+
+ 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 static Entry getEntry(File f) {
+ return new DirectoryEntry(f.getParentFile()).get(f);
+ }
+
+
+ @Override
+ public boolean isFile() {
+ return false;
+ }
+
+ public List getContents() {
+
+ loadChildren();
+ return children;
+ }
+
+ @Override
+ public Thumbnail getThumbnail() {
+ loadChildren();
+ return super.getThumbnail();
+ }
+
+ @Override
+ public String getCaption() {
+ loadChildren();
+ return super.getCaption();
+ }
+
+ 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;
+ }
+
+ 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);
+ }
+ }
+
+ boolean isCacheCurrent() {
+ return cache.exists() && cache.isFile() && cache.canRead() && file.lastModified() <= cache.lastModified();
+ }
+
+ Properties loadFromFile(File propFile) throws IOException {
+
+ Properties props = new Properties();
+ props.load(new FileInputStream(propFile));
+ return props;
+ }
+
+ Properties generateCache() throws MetadataException, JpegProcessingException, IOException {
+
+ Properties props = new Properties();
+ generateFileEntries(props);
+ props.store(new FileOutputStream(cache), "Extra Comments");
+ return props;
+ }
+
+
+ void generateFileEntries(Properties props)
+ throws IOException, MetadataException, JpegProcessingException {
+
+ File[] files = file.listFiles();
+ for (File f : files) {
+
+ String name = f.getName();
+ if (f.isDirectory()) {
+ if ("CVS".equals(name)) {
+ 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);
+ }
+ }
+ }
+
+
+ private void generateDirectoryProperties(Properties props, File f) {
+
+ 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");
+
+ Metadata 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 = exifDirectory.getDate(ExifDirectory.TAG_DATETIME_ORIGINAL);
+ 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;
+ }
+ 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;
+ }
+ }
+ }
+
+ Dimension decodeImageForDimensions(File file) throws IOException {
+
+ String suffix = null;
+ String name = file.getName();
+ if (name.indexOf('.') > 0) {
+ suffix = name.substring(name.lastIndexOf('.') + 1);
+ }
+ Iterator readers = ImageIO.getImageReadersBySuffix(suffix);
+ if (!readers.hasNext()) {
+ return null;
+ }
+ ImageReader reader = readers.next();
+ ImageInputStream iis = ImageIO.createImageInputStream(file);
+ reader.setInput(iis, true);
+ ImageReadParam param = reader.getDefaultReadParam();
+ BufferedImage img = reader.read(0, param);
+ return new Dimension(img.getWidth(), img.getHeight());
+ }
+
+ private void sort() {
+
+ Collections.sort(children, new Comparator() {
+
+ public int compare(Entry e1, Entry e2) {
+
+ if (!e1.isFile() && e2.isFile()) {
+ return -1;
+ } else if (e1.isFile() && !e2.isFile()) {
+ return +1;
+ }
+ Date d1 = e1.getDate();
+ Date d2 = e2.getDate();
+ if (d1 != null && d2 != null) {
+ return d1.compareTo(d2);
+ } else if (d1 != null) {
+ return -1;
+ } else if (d2 != null) {
+ return +1;
+ } else {
+ return 0;
+ }
+ }
+ });
+ }
+
+ void fillLinkedList() {
+
+ Entry prev = null;
+ for (Entry e : children) {
+ e.prev = prev;
+ if (prev != null) {
+ prev.next = e;
+ }
+ prev = e;
+ }
+ if (prev != null) {
+ prev.next = null;
+ }
+ }
+
+
+ private void populate(Properties props) throws ParseException {
+
+ String coverFileName = props.getProperty("cover");
+ HashMap entryMap = new HashMap();
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
+ 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);
+ 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")));
+ 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"));
+ entry.setThumbnail(thumbnail);
+ children.add(entry);
+ if (name != null && (coverFileName == null || name.equals(coverFileName))) {
+ setThumbnail(thumbnail);
+ }
+ }
+ } else if (key.startsWith("dir.")) {
+ String name = key.substring("dir.".length());
+ children.add(new DirectoryEntry(this, new File(file, name)));
+ }
+ }
+ }
+
+}
diff --git a/src/org/forkalsrud/album/exif/Entry.java b/src/org/forkalsrud/album/exif/Entry.java
index 193758c..148a83c 100644
--- a/src/org/forkalsrud/album/exif/Entry.java
+++ b/src/org/forkalsrud/album/exif/Entry.java
@@ -5,6 +5,7 @@
package org.forkalsrud.album.exif;
+import java.io.File;
import java.util.Date;
@@ -13,67 +14,45 @@ import java.util.Date;
*
* @author knut
*/
-public class Entry {
+public abstract class Entry {
- boolean isFile;
- String name;
- String path;
- Dimension size;
+ /**
+ * The object in the file system that this entry represents
+ */
+ File file;
+ Thumbnail thumbnail;
String caption;
Date date;
- int orientation;
Entry next;
Entry prev;
- String etag;
+ Entry parent;
-
- public boolean isFile() {
- return isFile;
- }
-
-
- public void setFile(boolean isFile) {
- this.isFile = isFile;
- }
+ protected Entry(File file) {
+ this.file = file;
+ }
+ public abstract boolean isFile();
/**
* @return Returns the name.
*/
public String getName() {
- return name;
+ return file.getName();
}
- /**
- * @param name The name to set.
- */
- public void setName(String name) {
- this.name = name;
- }
+ public Thumbnail getThumbnail() {
+ return thumbnail;
+ }
-
- /**
- * @return Returns the size.
- */
- public Dimension getSize() {
- return getOrientation() == 6 ? size.flip() : size;
- }
+ public void setThumbnail(Thumbnail thumbnail) {
+ this.thumbnail = thumbnail;
+ }
-
- /**
- * @param size The size to set.
- */
- public void setSize(Dimension size) {
- this.size = size;
- }
-
-
-
- /**
+ /**
* @return Returns the caption.
*/
public String getCaption() {
@@ -109,51 +88,16 @@ public class Entry {
- /**
- * @return Returns the orientation.
- */
- public int getOrientation() {
- return orientation;
- }
-
-
-
- /**
- * @param orientation The orientation to set.
- */
- public void setOrientation(int orientation) {
- this.orientation = orientation;
- }
-
public Entry prev() {
return prev;
}
public Entry next() {
return next;
}
-
-
-
- public String getEtag() {
- return etag;
+ public Entry parent() {
+ return parent;
}
-
-
-
- public void setEtag(String etag) {
- this.etag = etag;
+ public File getPath() {
+ return file;
}
-
-
- public String getPath() {
- return path;
- }
-
-
- public void setPath(String path) {
- this.path = path;
- }
-
-
-
}
diff --git a/src/org/forkalsrud/album/exif/EntryDao.java b/src/org/forkalsrud/album/exif/EntryDao.java
deleted file mode 100644
index 22b8bd3..0000000
--- a/src/org/forkalsrud/album/exif/EntryDao.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright 2008 Estalea, Inc. All rights reserved.
- * This software is the proprietary information of Estalea, Inc.
- */
-
-package org.forkalsrud.album.exif;
-
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.text.DecimalFormat;
-import java.text.NumberFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Properties;
-
-import javax.imageio.ImageIO;
-import javax.imageio.ImageReadParam;
-import javax.imageio.ImageReader;
-import javax.imageio.stream.ImageInputStream;
-
-import com.drew.imaging.jpeg.JpegMetadataReader;
-import com.drew.imaging.jpeg.JpegProcessingException;
-import com.drew.metadata.Directory;
-import com.drew.metadata.Metadata;
-import com.drew.metadata.MetadataException;
-import com.drew.metadata.exif.ExifDirectory;
-import com.drew.metadata.jpeg.JpegDirectory;
-
-
-/**
- * Responsible for translating entries to and from file
- *
- * @author knut
- */
-public class EntryDao {
-
- final static String CACHE_FILE = "cache.properties";
- final static String OVERRIDE_FILE = "album.properties";
-
- boolean isCurrent(File directory) {
-
- return directory.lastModified() <= new File(directory, CACHE_FILE).lastModified();
- }
-
- public Album read(File directory) throws FileNotFoundException, IOException, JpegProcessingException, MetadataException, ParseException {
-
- List entries = new ArrayList();
-
- Properties cachedProps = new Properties();
- File cache = new File(directory, CACHE_FILE);
- if (cache.exists() && cache.isFile() && cache.canRead() && isCurrent(directory)) {
- cachedProps.load(new FileInputStream(cache));
- } else {
- generateEntries(directory, cachedProps);
- }
- Properties overrideProps = new Properties();
- File override = new File(directory, OVERRIDE_FILE);
- if (override.exists() && override.isFile() && override.canRead()) {
- overrideProps.load(new FileInputStream(override));
- }
- Properties combined = new Properties();
- combined.putAll(cachedProps);
- combined.putAll(overrideProps);
- Entry cover = populate(combined, entries);
-
- Album alb = new Album();
- alb.setCover(cover);
- for (Entry e : entries) {
- alb.addContents(e);
- }
- generateDirectoryEntries(directory, alb);
- alb.sort();
- return alb;
- }
-
-
-
-
- private Entry populate(Properties cachedProps, List entries) throws ParseException {
-
- Entry cover = null;
- String coverFileName = cachedProps.getProperty("cover");
- HashMap entryMap = new HashMap();
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
- Iterator i = cachedProps.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)) {
- Entry entry = new Entry();
- entry.setFile(true);
- entry.setName(name);
- entry.setPath(name);
- entry.setDate(sdf.parse(cachedProps.getProperty("file." + name + ".captureDate")));
- entry.setSize(new Dimension(cachedProps.getProperty("file." + name + ".dimensions")));
- entry.setCaption(cachedProps.getProperty("file." + name + ".caption"));
- entry.setOrientation(Integer.parseInt(cachedProps.getProperty("file." + name + ".orientation")));
- entry.setEtag(cachedProps.getProperty("file." + name + ".etag"));
- entries.add(entry);
- if (name != null && name.equals(coverFileName)) {
- cover = entry;
- }
- }
- }
- }
- return cover;
- }
-
-
- public Entry readFile(File file) throws FileNotFoundException, IOException, JpegProcessingException, MetadataException, ParseException {
-
- List dir = read(file.getParentFile()).getContents();
- String name = file.getName();
- for (Entry e : dir) {
- if (name.equals(e.name)) {
- return e;
- }
- }
- return null;
- }
-
- void generateEntries(File directory, Properties cachedProps) throws JpegProcessingException, MetadataException, FileNotFoundException, IOException {
-
- generateFileEntries(directory, cachedProps);
- File dst = new File(directory, CACHE_FILE);
- if (directory.canWrite()) {
- cachedProps.store(new FileOutputStream(dst), "Extra Comments");
- }
- }
-
- void generateDirectoryEntries(File directory, Album album) throws FileNotFoundException, JpegProcessingException, MetadataException, IOException, ParseException {
-
- File[] dirs = directory.listFiles(new FileFilter() {
-
- public boolean accept(File file) {
-
- return !file.isHidden() && file.isDirectory() && !CACHE_FILE.equals(file.getName()) && !OVERRIDE_FILE.equals(file.getName());
- }
-
- });
- for (File dir : dirs) {
-
- Album childAlbum = read(dir);
- Entry childCover = childAlbum.getCover();
- if (childCover == null) {
- Iterator i = childAlbum.getContents().iterator();
- childCover = i.hasNext() ? i.next() : null;
- }
- if (childCover != null) {
- childCover.setFile(false);
- childCover.setName(dir.getName());
- childCover.setPath(dir.getName() + "/" + childCover.getPath());
- album.addContents(childCover);
- }
- }
- }
-
-
- void generateFileEntries(File directory, Properties cachedProps)
- throws JpegProcessingException, MetadataException, IOException {
- File[] files = directory.listFiles(new FileFilter() {
-
- public boolean accept(File file) {
-
- String name = file.getName();
- boolean isImageFile = name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".JPG");
- return isImageFile && !file.isHidden() && !file.isDirectory() && !CACHE_FILE.equals(file.getName()) && !OVERRIDE_FILE.equals(file.getName());
- }
-
- });
- SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
- NumberFormat nf = new DecimalFormat("0");
-
- for (File f : files) {
- String name = f.getName();
- String base = "file." + name + ".";
- boolean hasDate = false;
- boolean hasOrientation = false;
- boolean hasDim = false;
-
- Metadata metadata = JpegMetadataReader.readMetadata(f);
- Directory exifDirectory = metadata.getDirectory(ExifDirectory.class);
- if (exifDirectory.containsTag(ExifDirectory.TAG_ORIENTATION)) {
- int orientation = exifDirectory.getInt(ExifDirectory.TAG_ORIENTATION);
- cachedProps.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);
- cachedProps.setProperty(base + "dimensions", new Dimension(width, height).toString());
- hasDim = true;
- }
- if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) {
- Date captureDate = exifDirectory.getDate(ExifDirectory.TAG_DATETIME_ORIGINAL);
- cachedProps.setProperty(base + "captureDate", sdf.format(captureDate));
- hasDate = true;
- }
- if (exifDirectory.containsTag(ExifDirectory.TAG_USER_COMMENT)) {
- String comment = exifDirectory.getString(ExifDirectory.TAG_USER_COMMENT);
- cachedProps.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);
- cachedProps.setProperty(base + "dimensions", new Dimension(width, height).toString());
- hasDim = true;
- }
- cachedProps.setProperty(base + "etag", Integer.toHexString(name.hashCode() + Long.valueOf(f.lastModified()).hashCode()));
- if (!hasDate) {
- cachedProps.setProperty(base + "captureDate", sdf.format(new Date(f.lastModified())));
- }
- if (!hasOrientation) {
- cachedProps.setProperty(base + "orientation", "1");
- }
- if (!hasDim) {
- Dimension dim = decodeImageForDimensions(f);
- if (dim != null) {
- cachedProps.setProperty(base + "dimensions", dim.toString());
- hasDim = true;
- }
- }
- }
- }
-
- Dimension decodeImageForDimensions(File file) throws IOException {
-
- String suffix = null;
- String name = file.getName();
- if (name.indexOf('.') > 0) {
- suffix = name.substring(name.lastIndexOf('.') + 1);
- }
- Iterator readers = ImageIO.getImageReadersBySuffix(suffix);
- if (!readers.hasNext()) {
- return null;
- }
- ImageReader reader = readers.next();
- ImageInputStream iis = ImageIO.createImageInputStream(file);
- reader.setInput(iis, true);
- ImageReadParam param = reader.getDefaultReadParam();
- BufferedImage img = reader.read(0, param);
- return new Dimension(img.getWidth(), img.getHeight());
- }
-
-
-}
diff --git a/src/org/forkalsrud/album/exif/FileEntry.java b/src/org/forkalsrud/album/exif/FileEntry.java
new file mode 100644
index 0000000..c73fbd7
--- /dev/null
+++ b/src/org/forkalsrud/album/exif/FileEntry.java
@@ -0,0 +1,29 @@
+/**
+ *
+ */
+package org.forkalsrud.album.exif;
+
+import java.io.File;
+
+/**
+ * @author knut
+ *
+ */
+public class FileEntry extends Entry {
+
+
+ public FileEntry(Entry parent, File file) {
+
+ super(file);
+ if (!file.isFile()) {
+ throw new RuntimeException("Use FileEntry only for files: " + file);
+ }
+ this.parent = parent;
+ }
+
+ @Override
+ public boolean isFile() {
+ return true;
+ }
+
+}
diff --git a/src/org/forkalsrud/album/exif/Thumbnail.java b/src/org/forkalsrud/album/exif/Thumbnail.java
new file mode 100644
index 0000000..c862f37
--- /dev/null
+++ b/src/org/forkalsrud/album/exif/Thumbnail.java
@@ -0,0 +1,71 @@
+/**
+ *
+ */
+package org.forkalsrud.album.exif;
+
+import java.io.File;
+
+/**
+ * @author knut
+ *
+ */
+public class Thumbnail {
+
+ File path;
+ Dimension size;
+ int orientation;
+ String etag;
+
+
+ public Thumbnail(File path) {
+ this.path = path;
+ }
+
+ /**
+ * @return Returns the orientation.
+ */
+ public int getOrientation() {
+ return orientation;
+ }
+
+
+
+ /**
+ * @param orientation The orientation to set.
+ */
+ public void setOrientation(int orientation) {
+ this.orientation = orientation;
+ }
+
+ /**
+ * @return Returns the size.
+ */
+ public Dimension getSize() {
+ return getOrientation() == 6 ? size.flip() : size;
+ }
+
+
+
+ /**
+ * @param size The size to set.
+ */
+ public void setSize(Dimension size) {
+ this.size = size;
+ }
+
+
+ public String getEtag() {
+ return etag;
+ }
+
+
+
+ public void setEtag(String etag) {
+ this.etag = etag;
+ }
+
+
+ public File getPath() {
+ return path;
+ }
+}
diff --git a/src/org/forkalsrud/album/web/AlbumServlet.java b/src/org/forkalsrud/album/web/AlbumServlet.java
index 5da1ff5..22d3899 100644
--- a/src/org/forkalsrud/album/web/AlbumServlet.java
+++ b/src/org/forkalsrud/album/web/AlbumServlet.java
@@ -5,9 +5,7 @@ import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
-import java.text.ParseException;
import java.util.Iterator;
-import java.util.List;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
@@ -22,92 +20,120 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.forkalsrud.album.exif.Dimension;
+import org.forkalsrud.album.exif.DirectoryEntry;
import org.forkalsrud.album.exif.Entry;
-import org.forkalsrud.album.exif.EntryDao;
-
-import com.drew.imaging.jpeg.JpegProcessingException;
-import com.drew.metadata.MetadataException;
+import org.forkalsrud.album.exif.FileEntry;
+import org.forkalsrud.album.exif.Thumbnail;
public class AlbumServlet
extends HttpServlet
{
- String basePath = "photos";
- EntryDao dao;
+ File base;
+ String basePrefix;
@Override
public void init()
throws ServletException
{
System.out.println("in init of Album");
- dao = new EntryDao();
+ base = new File(getServletConfig().getInitParameter("base")).getAbsoluteFile();
+ basePrefix = "/" + base.getName();
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
{
+ req.setAttribute("assets", req.getContextPath() + "/assets");
+ req.setAttribute("req", req);
+ req.setAttribute("base", req.getContextPath() + req.getServletPath());
+ req.setAttribute("mapper", new Mapper());
String pathInfo = req.getPathInfo();
+ if (pathInfo != null && pathInfo.startsWith(basePrefix)) {
+ pathInfo = pathInfo.substring(basePrefix.length());
+ } else {
+ res.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
String page = null;
+ if (pathInfo == null || "/".equals(pathInfo)) {
+ page = "album";
+ } else if (pathInfo.endsWith(".album")) {
+ pathInfo = pathInfo.substring(0, pathInfo.length() - ".album".length());
+ page = "album";
+ }
+ if (page != null) {
+ DirectoryEntry entry = null;
+ entry = (DirectoryEntry)resolveEntry(pathInfo);
+ handleAlbum(req, res, entry);
+ return;
+ }
+
if (pathInfo.endsWith(".photo")) {
pathInfo = pathInfo.substring(0, pathInfo.length() - ".photo".length());
page = "photo";
+ handlePhoto(req, res, (FileEntry)resolveEntry(pathInfo));
+ return;
}
+
+ /*
int parentPos = pathInfo.substring(0, pathInfo.length() - 1).lastIndexOf('/');
if (parentPos >= 0) {
req.setAttribute("parent", req.getServletPath() + pathInfo.substring(0, parentPos) + "/");
}
req.setAttribute("assets", "/" + req.getContextPath() + "assets");
- File file = new File(basePath + pathInfo);
-// System.out.println("path=" + req.getContextPath());
+ */
+ File file = new File(base, pathInfo);
if (!file.canRead()) {
res.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
}
- if (file.isDirectory()) {
- if (!pathInfo.endsWith("/")) {
- res.sendRedirect(req.getContextPath() + req.getRequestURI() + "/");
- return;
- }
- try {
- List entries = dao.read(file).getContents();
- res.setContentType("text/html");
- req.setAttribute("directory", file.getName());
- req.setAttribute("path", req.getServletPath());
- req.setAttribute("entries", entries);
- RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/directory.vm");
- rd.forward(req, res);
-
- } catch (Exception e) {
- throw new RuntimeException("sadness", e);
- }
- } else if (req.getParameter("size") != null) {
+ if (req.getParameter("size") != null) {
try {
res.setContentType("image/jpeg");
- scaleImage(req, res, file, req.getParameter("size"));
+ FileEntry e = (FileEntry)DirectoryEntry.getEntry(file);
+ scaleImage(req, res, file, e.getThumbnail(), req.getParameter("size"));
} catch (Exception e) {
throw new RuntimeException("sadness", e);
}
- } else if ("photo".equals(page)) {
- try {
- Entry e = dao.readFile(file);
- req.setAttribute("entry", e);
- req.setAttribute("prev", e.prev());
- req.setAttribute("next", e.next());
- RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
- rd.forward(req, res);
- } catch (Exception e) {
- throw new RuntimeException("sadness", e);
- }
- } else {
- res.setStatus(HttpServletResponse.SC_NOT_FOUND);
- }
+ return;
+ }
+ res.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
- boolean etagMatches(HttpServletRequest req, HttpServletResponse res, Entry entry, String size) {
- String reqEtag = req.getHeader("If-None-Match");
- String fileEtag = entry.getEtag() + "-" + size;
+ void handlePhoto(HttpServletRequest req, HttpServletResponse res, FileEntry entry) {
+ try {
+ res.setContentType("text/html");
+ req.setAttribute("entry", entry);
+ RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
+ rd.forward(req, res);
+ } catch (Exception ex) {
+ throw new RuntimeException("sadness", ex);
+ }
+ }
+
+ void handleAlbum(HttpServletRequest req, HttpServletResponse res, DirectoryEntry entry) {
+ try {
+ res.setContentType("text/html");
+ req.setAttribute("entry", entry);
+ RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
+ rd.forward(req, res);
+ } catch (Exception e) {
+ throw new RuntimeException("sadness", e);
+ }
+ }
+
+
+ boolean etagMatches(HttpServletRequest req, HttpServletResponse res, Thumbnail thumbnail, String size) {
+
+ String cacheControl = req.getHeader("Cache-Control");
+ if ("max-age=0".equals(cacheControl)) {
+ return false;
+ }
+ String reqEtag = req.getHeader("If-None-Match");
+ String fileEtag = thumbnail.getEtag() + "-" + size;
if (reqEtag != null) {
return reqEtag.equals(fileEtag);
}
@@ -115,14 +141,13 @@ public class AlbumServlet
return false;
}
- void scaleImage(HttpServletRequest req, HttpServletResponse res, File file, String size) throws IOException, JpegProcessingException, MetadataException, ParseException {
+ void scaleImage(HttpServletRequest req, HttpServletResponse res, File file, Thumbnail thumbnail, String size) throws IOException {
- Entry entry = dao.readFile(file);
- if (etagMatches(req, res, entry, size)) {
+ if (etagMatches(req, res, thumbnail, size)) {
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
- Dimension orig = entry.getSize();
+ Dimension orig = thumbnail.getSize();
Dimension outd;
if (size.endsWith("h")) {
int height = Integer.parseInt(size.substring(0, size.length() - 1));
@@ -152,7 +177,7 @@ public class AlbumServlet
// Recalculate scale after sub-sampling was applied
double scale;
AffineTransform xform;
- if (entry.getOrientation() == 6) {
+ if (thumbnail.getOrientation() == 6) {
xform = AffineTransform.getTranslateInstance(outd.getWidth() / 2d, outd.getHeight() / 2d);
xform.rotate(Math.PI / 2);
scale = (double)outd.getHeight() / img.getWidth();
@@ -176,11 +201,44 @@ public class AlbumServlet
}
+ Entry resolveEntry(String pathInfo) {
+
+ if (pathInfo == null || "/".equals(pathInfo)) return resolve(base);
+ return resolve(new File(base, pathInfo));
+ }
+
+ Entry resolve(File file) {
+
+ if (base.equals(file.getAbsoluteFile())) {
+ return new DirectoryEntry(null, file);
+ } else {
+ return ((DirectoryEntry)resolve(file.getParentFile())).get(file);
+ }
+ }
+
+
@Override
public String getServletInfo() {
return "Display of org.forkalsrud.album";
}
+
+ public class Mapper {
+
+ public String map(File file) {
+
+ StringBuilder buf = new StringBuilder();
+ return appendFile(buf, file).toString();
+ }
+
+ StringBuilder appendFile(StringBuilder buf, File file) {
+ if (base.equals(file.getAbsoluteFile())) {
+ return buf.append("/").append(base.getName());
+ } else {
+ return appendFile(buf, file.getParentFile()).append('/').append(file.getName());
+ }
+ }
+ }
}
// eof
diff --git a/webapp/WEB-INF/velocity/directory.vm b/webapp/WEB-INF/velocity/directory.vm
deleted file mode 100644
index 920d4fb..0000000
--- a/webapp/WEB-INF/velocity/directory.vm
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
- $directory
-
-
-
-
-#if($prev && $prev.isFile())
#else
#end#if($parent)
#else
#end#if($next && $next.isFile())
#else
#end$directory
-
-#set($thmb = 150)
-#foreach($entry in $entries)
-#set($dim = $entry.size.scaled($thmb))
-
-
$entry.name
-#if($entry.isFile())
-

-#else
-

-#end
-
$!entry.caption
-
-#end
-
-
-
diff --git a/webapp/WEB-INF/velocity/photo.vm b/webapp/WEB-INF/velocity/photo.vm
index 87a7c4b..c82e3aa 100644
--- a/webapp/WEB-INF/velocity/photo.vm
+++ b/webapp/WEB-INF/velocity/photo.vm
@@ -14,9 +14,9 @@
margin: 10px auto;
text-align: center;
}
- h1 {
+ h1 {
text-align: left;
- }
+ }
a:link, a:visited {
text-decoration: none;
color: #4c4c4c;
@@ -27,8 +27,12 @@
color: #6a6a6a;
}
div.photo {
- align: center;
- height: 200px;
+ align: center;
+ height: 200px;
+ }
+ div.grid {
+ float: left;
+ height: 150px;
}
img {
padding: 5px;
@@ -36,21 +40,37 @@
.nav {
border: 0 none;
padding: 0px;
- vertical-align: middle;
+ vertical-align: middle;
}
-#if($prev && $prev.isFile())
#else
#end#if($parent)
#else
#end#if($next && $next.isFile())
#else
#end$entry.name
+#macro(navlink $entry)${base}$mapper.map(${entry.getPath()}).#if($entry.isFile())photo#{else}album#end#end
+#macro(navbutton $entry $direction)#if($entry)
#else
#end#end
+#navbutton(${entry.prev()} "left")#navbutton(${entry.parent()} "up")#navbutton(${entry.next()} "right") $entry.name
+
+#if($entry.isFile())
#set($thmb = 480)
-#set($dim = $entry.size.scaled($thmb))
+#set($dim = $entry.thumbnail.size.scaled($thmb))
+#set($thpath = $mapper.map(${entry.thumbnail.getPath()}))
+#set($enpath = $mapper.map(${entry.getPath()}))
-
-

+
$!entry.caption
-
+#else
+#set($thmb = 150)
+#foreach($en in $entry.getContents())
+#set($dim = $en.thumbnail.size.scaled($thmb))
+#set($thpath = $mapper.map(${en.thumbnail.getPath()}))
+
+
$en.name
+

+
$!en.caption
+
+#end
+#end