Added prev/next and etag for caching of images
This commit is contained in:
parent
c4b1a3ee5f
commit
68c297603a
5 changed files with 108 additions and 10 deletions
BIN
photos/irony.jpg
Normal file
BIN
photos/irony.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -20,6 +20,9 @@ public class Entry {
|
||||||
String caption;
|
String caption;
|
||||||
Date date;
|
Date date;
|
||||||
int orientation;
|
int orientation;
|
||||||
|
Entry next;
|
||||||
|
Entry prev;
|
||||||
|
String etag;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -108,4 +111,25 @@ public class Entry {
|
||||||
public void setOrientation(int orientation) {
|
public void setOrientation(int orientation) {
|
||||||
this.orientation = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package org.forkalsrud.album.exif;
|
package org.forkalsrud.album.exif;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileFilter;
|
import java.io.FileFilter;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
|
@ -24,6 +25,11 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
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.JpegMetadataReader;
|
||||||
import com.drew.imaging.jpeg.JpegProcessingException;
|
import com.drew.imaging.jpeg.JpegProcessingException;
|
||||||
import com.drew.metadata.Directory;
|
import com.drew.metadata.Directory;
|
||||||
|
|
@ -43,13 +49,18 @@ public class EntryDao {
|
||||||
final static String CACHE_FILE = "cache.properties";
|
final static String CACHE_FILE = "cache.properties";
|
||||||
final static String OVERRIDE_FILE = "album.properties";
|
final static String OVERRIDE_FILE = "album.properties";
|
||||||
|
|
||||||
|
boolean isCurrent(File directory) {
|
||||||
|
|
||||||
|
return directory.lastModified() <= new File(directory, CACHE_FILE).lastModified();
|
||||||
|
}
|
||||||
|
|
||||||
public List<Entry> read(File directory) throws FileNotFoundException, IOException, JpegProcessingException, MetadataException, ParseException {
|
public List<Entry> read(File directory) throws FileNotFoundException, IOException, JpegProcessingException, MetadataException, ParseException {
|
||||||
|
|
||||||
List<Entry> entries = new ArrayList<Entry>();
|
List<Entry> entries = new ArrayList<Entry>();
|
||||||
|
|
||||||
Properties cachedProps = new Properties();
|
Properties cachedProps = new Properties();
|
||||||
File cache = new File(directory, CACHE_FILE);
|
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));
|
cachedProps.load(new FileInputStream(cache));
|
||||||
} else {
|
} else {
|
||||||
generateEntries(directory, cachedProps);
|
generateEntries(directory, cachedProps);
|
||||||
|
|
@ -80,9 +91,25 @@ public class EntryDao {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
fillLinkedList(entries);
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fillLinkedList(List<Entry> 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<Entry> entries) throws ParseException {
|
private void populate(Properties cachedProps, List<Entry> entries) throws ParseException {
|
||||||
|
|
||||||
|
|
@ -100,6 +127,7 @@ public class EntryDao {
|
||||||
entry.setSize(new Dimension(cachedProps.getProperty("file." + name + ".dimensions")));
|
entry.setSize(new Dimension(cachedProps.getProperty("file." + name + ".dimensions")));
|
||||||
entry.setCaption(cachedProps.getProperty("file." + name + ".caption"));
|
entry.setCaption(cachedProps.getProperty("file." + name + ".caption"));
|
||||||
entry.setOrientation(Integer.parseInt(cachedProps.getProperty("file." + name + ".orientation")));
|
entry.setOrientation(Integer.parseInt(cachedProps.getProperty("file." + name + ".orientation")));
|
||||||
|
entry.setEtag(cachedProps.getProperty("file." + name + ".etag"));
|
||||||
entries.add(entry);
|
entries.add(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,6 +165,7 @@ public class EntryDao {
|
||||||
String base = "file." + name + ".";
|
String base = "file." + name + ".";
|
||||||
boolean hasDate = false;
|
boolean hasDate = false;
|
||||||
boolean hasOrientation = false;
|
boolean hasOrientation = false;
|
||||||
|
boolean hasDim = false;
|
||||||
|
|
||||||
Metadata metadata = JpegMetadataReader.readMetadata(f);
|
Metadata metadata = JpegMetadataReader.readMetadata(f);
|
||||||
Directory exifDirectory = metadata.getDirectory(ExifDirectory.class);
|
Directory exifDirectory = metadata.getDirectory(ExifDirectory.class);
|
||||||
|
|
@ -150,6 +179,7 @@ public class EntryDao {
|
||||||
int width = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_WIDTH);
|
int width = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_WIDTH);
|
||||||
int height = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT);
|
int height = exifDirectory.getInt(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT);
|
||||||
cachedProps.setProperty(base + "dimensions", new Dimension(width, height).toString());
|
cachedProps.setProperty(base + "dimensions", new Dimension(width, height).toString());
|
||||||
|
hasDim = true;
|
||||||
}
|
}
|
||||||
if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) {
|
if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) {
|
||||||
Date captureDate = exifDirectory.getDate(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 width = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_WIDTH);
|
||||||
int height = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT);
|
int height = jpegDirectory.getInt(JpegDirectory.TAG_JPEG_IMAGE_HEIGHT);
|
||||||
cachedProps.setProperty(base + "dimensions", new Dimension(width, height).toString());
|
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) {
|
if (!hasDate) {
|
||||||
cachedProps.setProperty(base + "captureDate", sdf.format(new Date(f.lastModified())));
|
cachedProps.setProperty(base + "captureDate", sdf.format(new Date(f.lastModified())));
|
||||||
}
|
}
|
||||||
if (!hasOrientation) {
|
if (!hasOrientation) {
|
||||||
cachedProps.setProperty(base + "orientation", "1");
|
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);
|
File dst = new File(directory, CACHE_FILE);
|
||||||
if (directory.canWrite()) {
|
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<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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,8 @@ public class AlbumServlet
|
||||||
try {
|
try {
|
||||||
Entry e = dao.readFile(file);
|
Entry e = dao.readFile(file);
|
||||||
req.setAttribute("entry", e);
|
req.setAttribute("entry", e);
|
||||||
|
req.setAttribute("prev", e.prev());
|
||||||
|
req.setAttribute("next", e.next());
|
||||||
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
|
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
|
||||||
rd.forward(req, res);
|
rd.forward(req, res);
|
||||||
} catch (Exception e) {
|
} 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 {
|
void scaleImage(HttpServletRequest req, HttpServletResponse res, File file, String size) throws IOException, JpegProcessingException, MetadataException, ParseException {
|
||||||
|
|
||||||
List<Entry> entries = dao.read(file.getParentFile());
|
Entry entry = dao.readFile(file);
|
||||||
Entry entry = null;
|
if (etagMatches(req, res, entry, size)) {
|
||||||
String myName = file.getName();
|
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
for (Entry e : entries) {
|
return;
|
||||||
if (myName.equals(e.getName())) {
|
|
||||||
entry = e;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Dimension orig = entry.getSize();
|
Dimension orig = entry.getSize();
|
||||||
Dimension outd;
|
Dimension outd;
|
||||||
|
|
@ -134,7 +141,7 @@ public class AlbumServlet
|
||||||
ImageInputStream iis = ImageIO.createImageInputStream(file);
|
ImageInputStream iis = ImageIO.createImageInputStream(file);
|
||||||
reader.setInput(iis, true);
|
reader.setInput(iis, true);
|
||||||
ImageReadParam param = reader.getDefaultReadParam();
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
if (true /* subsampling */) {
|
if (false /* subsampling */) {
|
||||||
double subSampleScale = (double)outd.getWidth() / orig.getWidth();
|
double subSampleScale = (double)outd.getWidth() / orig.getWidth();
|
||||||
int subSampling = (int)Math.floor(0.25d/subSampleScale);
|
int subSampling = (int)Math.floor(0.25d/subSampleScale);
|
||||||
if (subSampling > 1) {
|
if (subSampling > 1) {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,15 @@
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<center>
|
||||||
|
#if($prev)
|
||||||
|
<a href="${prev.name}.photo"><--</a>
|
||||||
|
#end
|
||||||
|
#if($next)
|
||||||
|
<a href="${next.name}.photo">--></a>
|
||||||
|
#end
|
||||||
|
</center>
|
||||||
|
|
||||||
#set($thmb = 480)
|
#set($thmb = 480)
|
||||||
#set($dim = $entry.size.scaled($thmb))
|
#set($dim = $entry.size.scaled($thmb))
|
||||||
<div class="photo">
|
<div class="photo">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue