diff --git a/photos/irony.jpg b/photos/irony.jpg new file mode 100644 index 0000000..48ffcdd Binary files /dev/null and b/photos/irony.jpg differ diff --git a/src/org/forkalsrud/album/exif/Entry.java b/src/org/forkalsrud/album/exif/Entry.java index 4b774ae..e1a2874 100644 --- a/src/org/forkalsrud/album/exif/Entry.java +++ b/src/org/forkalsrud/album/exif/Entry.java @@ -20,6 +20,9 @@ public class Entry { String caption; Date date; int orientation; + Entry next; + Entry prev; + String etag; /** @@ -108,4 +111,25 @@ public class Entry { public void setOrientation(int orientation) { this.orientation = orientation; } + + public Entry prev() { + return prev; + } + public Entry next() { + return next; + } + + + + public String getEtag() { + return etag; + } + + + + public void setEtag(String etag) { + this.etag = etag; + } + + } diff --git a/src/org/forkalsrud/album/exif/EntryDao.java b/src/org/forkalsrud/album/exif/EntryDao.java index a02e3b8..393728f 100644 --- a/src/org/forkalsrud/album/exif/EntryDao.java +++ b/src/org/forkalsrud/album/exif/EntryDao.java @@ -5,6 +5,7 @@ package org.forkalsrud.album.exif; +import java.awt.image.BufferedImage; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; @@ -24,6 +25,11 @@ 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; @@ -43,13 +49,18 @@ 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 List 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()) { + if (cache.exists() && cache.isFile() && cache.canRead() && isCurrent(directory)) { cachedProps.load(new FileInputStream(cache)); } else { generateEntries(directory, cachedProps); @@ -80,9 +91,25 @@ public class EntryDao { } } }); + fillLinkedList(entries); return entries; } + void fillLinkedList(List entries) { + + Entry prev = null; + for (Entry e : entries) { + e.prev = prev; + if (prev != null) { + prev.next = e; + } + prev = e; + } + if (prev != null) { + prev.next = null; + } + } + private void populate(Properties cachedProps, List entries) throws ParseException { @@ -100,6 +127,7 @@ public class EntryDao { 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); } } @@ -137,6 +165,7 @@ public class EntryDao { String base = "file." + name + "."; boolean hasDate = false; boolean hasOrientation = false; + boolean hasDim = false; Metadata metadata = JpegMetadataReader.readMetadata(f); Directory exifDirectory = metadata.getDirectory(ExifDirectory.class); @@ -150,6 +179,7 @@ public class EntryDao { 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); @@ -166,13 +196,22 @@ public class EntryDao { 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; + } + } } File dst = new File(directory, CACHE_FILE); if (directory.canWrite()) { @@ -180,5 +219,24 @@ public class EntryDao { } } + 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/web/AlbumServlet.java b/src/org/forkalsrud/album/web/AlbumServlet.java index a6882a0..f4f9bd3 100644 --- a/src/org/forkalsrud/album/web/AlbumServlet.java +++ b/src/org/forkalsrud/album/web/AlbumServlet.java @@ -94,6 +94,8 @@ public class AlbumServlet 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) { @@ -104,17 +106,22 @@ public class AlbumServlet } } + boolean etagMatches(HttpServletRequest req, HttpServletResponse res, Entry entry, String size) { + String reqEtag = req.getHeader("If-None-Match"); + String fileEtag = entry.getEtag() + "-" + size; + if (reqEtag != null) { + return reqEtag.equals(fileEtag); + } + res.setHeader("ETag", fileEtag); + return false; + } void scaleImage(HttpServletRequest req, HttpServletResponse res, File file, String size) throws IOException, JpegProcessingException, MetadataException, ParseException { - List entries = dao.read(file.getParentFile()); - Entry entry = null; - String myName = file.getName(); - for (Entry e : entries) { - if (myName.equals(e.getName())) { - entry = e; - break; - } + Entry entry = dao.readFile(file); + if (etagMatches(req, res, entry, size)) { + res.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return; } Dimension orig = entry.getSize(); Dimension outd; @@ -134,7 +141,7 @@ public class AlbumServlet ImageInputStream iis = ImageIO.createImageInputStream(file); reader.setInput(iis, true); ImageReadParam param = reader.getDefaultReadParam(); - if (true /* subsampling */) { + if (false /* subsampling */) { double subSampleScale = (double)outd.getWidth() / orig.getWidth(); int subSampling = (int)Math.floor(0.25d/subSampleScale); if (subSampling > 1) { diff --git a/webapp/WEB-INF/velocity/photo.vm b/webapp/WEB-INF/velocity/photo.vm index fc2dd9f..bace66a 100644 --- a/webapp/WEB-INF/velocity/photo.vm +++ b/webapp/WEB-INF/velocity/photo.vm @@ -34,6 +34,15 @@ +
+#if($prev) + <-- +#end +#if($next) + --> +#end +
+ #set($thmb = 480) #set($dim = $entry.size.scaled($thmb))