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/*