From 01e246023de1638370f6353053db21561b62551f Mon Sep 17 00:00:00 2001 From: knut Date: Sun, 11 Jan 2009 08:57:28 +0000 Subject: [PATCH] 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 --- .classpath | 13 +- src/org/forkalsrud/album/exif/Album.java | 84 ----- .../forkalsrud/album/exif/DirectoryEntry.java | 331 ++++++++++++++++++ src/org/forkalsrud/album/exif/Entry.java | 104 ++---- src/org/forkalsrud/album/exif/EntryDao.java | 260 -------------- src/org/forkalsrud/album/exif/FileEntry.java | 29 ++ src/org/forkalsrud/album/exif/Thumbnail.java | 71 ++++ .../forkalsrud/album/web/AlbumServlet.java | 162 ++++++--- webapp/WEB-INF/velocity/directory.vm | 62 ---- webapp/WEB-INF/velocity/photo.vm | 40 ++- webapp/WEB-INF/web.xml | 6 +- 11 files changed, 601 insertions(+), 561 deletions(-) delete mode 100644 src/org/forkalsrud/album/exif/Album.java create mode 100644 src/org/forkalsrud/album/exif/DirectoryEntry.java delete mode 100644 src/org/forkalsrud/album/exif/EntryDao.java create mode 100644 src/org/forkalsrud/album/exif/FileEntry.java create mode 100644 src/org/forkalsrud/album/exif/Thumbnail.java delete mode 100644 webapp/WEB-INF/velocity/directory.vm 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 diff --git a/webapp/WEB-INF/web.xml b/webapp/WEB-INF/web.xml index 2a7c8dc..e3677c2 100644 --- a/webapp/WEB-INF/web.xml +++ b/webapp/WEB-INF/web.xml @@ -15,6 +15,10 @@ album org.forkalsrud.album.web.AlbumServlet + + base + photos + @@ -29,7 +33,7 @@ album - /photos/* + /album/*