Thorough rework of paths. Now every album (including top level) end in name.album, where name is the name of the directory.

Removed EntryDao and moved most of the logic to DirectoryEntry
This commit is contained in:
knut 2009-01-11 08:57:28 +00:00
parent 167e214a38
commit 01e246023d
11 changed files with 601 additions and 561 deletions

View file

@ -8,21 +8,10 @@
<classpathentry kind="var" path="MAVEN_REPO/commons-digester/jars/commons-digester-1.7.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/commons-collections/jars/commons-collections-3.1.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/commons-lang/jars/commons-lang-2.0.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/postgresql/jars/postgresql-8.1-408.jdbc3.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/velocity/jars/velocity-1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/velocity-tools/jars/velocity-tools-view-1.2.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/com.drew/jars/metadata-extractor-2.3.1.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/servletapi/jars/servletapi-2.4.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/junit/jars/junit-4.1.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/resin-3.1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/resin-util-3.1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/jta-101-3.1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/webbeans-16-3.1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/ejb-15-3.1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/jpa-15-3.1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/jsdk-15-3.1.5.jar" sourcepath="/MAVEN_REPO/resin-3.1/sources/resin-3.1.5-src.zip"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/hessian-3.1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/jca-15-3.1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/resin-3.1/jars/jstl-11-3.1.5.jar"/>
<classpathentry kind="var" path="MAVEN_REPO/junit/jars/junit-4.4.jar"/>
<classpathentry kind="output" path="build/eclipse"/>
</classpath>

View file

@ -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<Entry> contents = new ArrayList<Entry>();
public Entry getCover() {
return cover;
}
public List<Entry> 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<Entry>() {
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;
}
}
}

View file

@ -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<Entry> children = new ArrayList<Entry>();
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<Entry> 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<ImageReader> 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<Entry>() {
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<String, Entry> entryMap = new HashMap<String, Entry>();
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)));
}
}
}
}

View file

@ -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;
}
}

View file

@ -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<Entry> entries = new ArrayList<Entry>();
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<Entry> entries) throws ParseException {
Entry cover = null;
String coverFileName = cachedProps.getProperty("cover");
HashMap<String, Entry> entryMap = new HashMap<String, Entry>();
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<Entry> 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<Entry> 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<ImageReader> 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());
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<Entry> 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

View file

@ -1,62 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width = 600" />
<title>$directory</title>
<style type="text/css">
body {
font-size: 11px;
font-family: "Lucida Grande", Arial, sans-serif;
color: #6d6d6d;
background: #acc95f;
margin: 10px auto;
text-align: center;
}
h1 {
text-align: left;
}
a:link, a:visited {
text-decoration: none;
color: #4c4c4c;
font-weight: 700;
}
a:hover, a:focus {
text-decoration: underline;
color: #6a6a6a;
}
div.home_entry_table {
float: left;
height: 200px;
}
img {
padding: 5px;
}
.nav {
border: 0 none;
padding: 0px;
vertical-align: middle;
}
</style>
</head>
<body>
<h1>#if($prev && $prev.isFile())<a href="${prev.name}.photo"><img class="nav" src="${assets}/left.png"/></a>#else<img class="nav" src="${assets}/left-inactive.png"/>#end#if($parent)<a href="$parent"><img class="nav" src="${assets}/up.png"/></a>#else<img class="nav" src="${assets}/up-inactive.png"/>#end#if($next && $next.isFile())<a href="${next.name}.photo"><img class="nav" src="${assets}/right.png"/></a>#else<img class="nav" src="${assets}/right-inactive.png"/>#end$directory</h1>
<hr/>
#set($thmb = 150)
#foreach($entry in $entries)
#set($dim = $entry.size.scaled($thmb))
<div class="home_entry_table">
<span class="name">$entry.name</span><br/>
#if($entry.isFile())
<a href="${entry.name}.photo"><img src="$entry.path?size=$thmb" border="0" width="$dim.width" height="$dim.height"/></a><br/>
#else
<a href="${entry.name}/"><img src="$entry.path?size=$thmb" border="0" width="$dim.width" height="$dim.height"/></a><br/>
#end
<span class="caption">$!entry.caption</span>
</div>
#end
</body>
</html>

View file

@ -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;
}
</style>
</head>
<body>
<h1>#if($prev && $prev.isFile())<a href="${prev.name}.photo"><img class="nav" src="${assets}/left.png"/></a>#else<img class="nav" src="${assets}/left-inactive.png"/>#end#if($parent)<a href="$parent"><img class="nav" src="${assets}/up.png"/></a>#else<img class="nav" src="${assets}/up-inactive.png"/>#end#if($next && $next.isFile())<a href="${next.name}.photo"><img class="nav" src="${assets}/right.png"/></a>#else<img class="nav" src="${assets}/right-inactive.png"/>#end$entry.name</h1>
#macro(navlink $entry)${base}$mapper.map(${entry.getPath()}).#if($entry.isFile())photo#{else}album#end#end
#macro(navbutton $entry $direction)#if($entry)<a href="#navlink($entry)"><img class="nav" width="26" height="32" src="${assets}/${direction}.png"/></a>#else<img class="nav" width="26" height="32" src="${assets}/${direction}-inactive.png"/>#end#end
<h1>#navbutton(${entry.prev()} "left")#navbutton(${entry.parent()} "up")#navbutton(${entry.next()} "right") $entry.name</h1>
<hr/>
#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()}))
<div class="photo">
<span class="name"></span><br/>
<a href="$entry.name"><img src="$entry.name?size=$thmb" border="0" width="$dim.width" height="$dim.height"/></a><br/>
<a href="${base}${enpath}"><img src="${base}${thpath}?size=$thmb" border="0" width="$dim.width" height="$dim.height"/></a><br/>
<span class="caption">$!entry.caption</span>
</div>
#else
#set($thmb = 150)
#foreach($en in $entry.getContents())
#set($dim = $en.thumbnail.size.scaled($thmb))
#set($thpath = $mapper.map(${en.thumbnail.getPath()}))
<div class="grid">
<span class="name">$en.name</span><br/>
<a href="#navlink($en)"><img src="${base}${thpath}?size=${thmb}" border="0" width="${dim.width}" height="${dim.height}"/></a><br/>
<span class="caption">$!en.caption</span>
</div>
#end
#end
</body>
</html>

View file

@ -15,6 +15,10 @@
<servlet>
<servlet-name>album</servlet-name>
<servlet-class>org.forkalsrud.album.web.AlbumServlet</servlet-class>
<init-param>
<param-name>base</param-name>
<param-value>photos</param-value>
</init-param>
</servlet>
<servlet>
@ -29,7 +33,7 @@
<servlet-mapping>
<servlet-name>album</servlet-name>
<url-pattern>/photos/*</url-pattern>
<url-pattern>/album/*</url-pattern>
</servlet-mapping>
</web-app>