better sorting of directories

This commit is contained in:
Erik Forkalsrud 2009-05-03 17:38:35 -07:00
parent 7386a1a36e
commit 0a7867cf14
4 changed files with 233 additions and 191 deletions

View file

@ -44,8 +44,8 @@ public class ComparatorFactory {
public int compare(Entry e1, Entry e2) {
Date d1 = e1.getDate();
Date d2 = e2.getDate();
Date d1 = e1.getEarliest();
Date d2 = e2.getEarliest();
if (d1 != null && d2 != null) {
return d1.compareTo(d2);
} else if (d1 != null) {

View file

@ -19,6 +19,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.imageio.ImageIO;
@ -43,223 +44,230 @@ public class DirectoryEntry extends Entry {
final static String OVERRIDE_FILE = "album.properties";
File cache;
List<Entry> children = new ArrayList<Entry>();
boolean childrenLoaded = false;
Comparator<Entry> sort = null;
List<Entry> children = new ArrayList<Entry>();
boolean childrenLoaded = false;
Comparator<Entry> sort = null;
Date earliest = null;
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(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 DirectoryEntry(Entry parent, File file) {
this(file);
this.parent = parent;
}
public static Entry getEntry(File f) {
return new DirectoryEntry(f.getParentFile()).get(f);
}
public static Entry getEntry(File f) {
return new DirectoryEntry(f.getParentFile()).get(f);
}
@Override
public boolean isFile() {
return false;
}
@Override
public boolean isFile() {
return false;
}
public List<Entry> getContents() {
public List<Entry> getContents() {
loadChildren();
return children;
}
loadChildren();
return children;
}
@Override
public Thumbnail getThumbnail() {
loadChildren();
return super.getThumbnail();
}
@Override
public Thumbnail getThumbnail() {
loadChildren();
return super.getThumbnail();
}
@Override
public String getCaption() {
loadChildren();
return super.getCaption();
}
@Override
public String getCaption() {
loadChildren();
return super.getCaption();
}
public void addContents(Entry entry) {
children.add(entry);
}
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;
}
public Entry get(File f) {
loadChildren();
for (Entry e : children) {
if (f.equals(e.file)) {
return e;
}
}
return null;
}
protected void loadChildren() {
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);
}
}
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() {
boolean isCacheCurrent() {
return cache.exists() && cache.isFile() && cache.canRead() && file.lastModified() <= cache.lastModified();
}
}
Properties loadFromFile(File propFile) throws IOException {
Properties loadFromFile(File propFile) throws IOException {
Properties props = new Properties();
props.load(new FileInputStream(propFile));
return props;
}
Properties props = new Properties();
props.load(new FileInputStream(propFile));
return props;
}
Properties generateCache() throws MetadataException, JpegProcessingException, IOException {
Properties generateCache() throws MetadataException, JpegProcessingException, IOException, ParseException {
long start = System.currentTimeMillis();
Properties props = new Properties();
generateFileEntries(props);
props.store(new FileOutputStream(cache), "Extra Comments");
long end = System.currentTimeMillis();
System.out.println("Time to generate properties for " + file.getPath() + ": " + (end - start)/1000d + " s");
long start = System.currentTimeMillis();
Properties props = new Properties();
generateFileEntries(props);
props.store(new FileOutputStream(cache), "Extra Comments");
long end = System.currentTimeMillis();
System.out.println("Time to generate properties for " + file.getPath() + ": " + (end - start)/1000d + " s");
return props;
}
return props;
}
void generateFileEntries(Properties props)
throws IOException, MetadataException, JpegProcessingException {
void generateFileEntries(Properties props)
throws IOException, MetadataException, JpegProcessingException, ParseException {
File[] files = file.listFiles();
for (File f : files) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
File[] files = file.listFiles();
for (File f : files) {
String name = f.getName();
if (f.isDirectory()) {
if ("CVS".equals(name)) {
continue;
}
if (f.isHidden()) {
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);
}
}
}
String name = f.getName();
if (f.isDirectory()) {
if ("CVS".equals(name)) {
continue;
}
if (f.isHidden()) {
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")) {
String base = "file." + f.getName() + ".";
Map<String, String> p = generateThumbnailProperties(f);
for (Map.Entry<String, String> e : p.entrySet()) {
props.setProperty(base + e.getKey(), e.getValue());
}
}
}
}
private void generateDirectoryProperties(Properties props, File f) {
private void generateDirectoryProperties(Properties props, File f) {
String name = f.getName();
props.setProperty("dir." + name, "present");
}
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");
private Map<String, String> generateThumbnailProperties(File f)
throws JpegProcessingException, MetadataException, IOException {
HashMap<String, String> props = new HashMap<String, String>();
String name = f.getName();
boolean hasDate = false;
boolean hasOrientation = false;
boolean hasDim = false;
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
NumberFormat nf = new DecimalFormat("0");
Metadata metadata;
try {
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 = getExifDate(exifDirectory, ExifDirectory.TAG_DATETIME_ORIGINAL);
if (captureDate != null) {
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;
}
Metadata metadata;
try {
metadata = JpegMetadataReader.readMetadata(f);
Directory exifDirectory = metadata.getDirectory(ExifDirectory.class);
if (exifDirectory.containsTag(ExifDirectory.TAG_ORIENTATION)) {
int orientation = exifDirectory.getInt(ExifDirectory.TAG_ORIENTATION);
props.put("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.put("dimensions", new Dimension(width, height).toString());
hasDim = true;
}
if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) {
Date captureDate = getExifDate(exifDirectory, ExifDirectory.TAG_DATETIME_ORIGINAL);
if (captureDate != null) {
props.put("captureDate", sdf.format(captureDate));
hasDate = true;
}
}
if (exifDirectory.containsTag(ExifDirectory.TAG_USER_COMMENT)) {
String comment = exifDirectory.getString(ExifDirectory.TAG_USER_COMMENT);
props.put("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.put("dimensions", new Dimension(width, height).toString());
hasDim = true;
}
} catch (Exception e) {
throw new RuntimeException("problem reading file " + f.getPath(), e);
}
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;
}
}
}
props.put("etag", Integer.toHexString(name.hashCode() + Long.valueOf(f.lastModified()).hashCode()));
if (!hasDate) {
props.put("captureDate", sdf.format(new Date(f.lastModified())));
}
if (!hasOrientation) {
props.put("orientation", "1");
}
if (!hasDim) {
Dimension dim = decodeImageForDimensions(f);
if (dim != null) {
props.put("dimensions", dim.toString());
hasDim = true;
}
}
return props;
}
private Date getExifDate(Directory exifDirectory, int tagName) throws MetadataException {
private Date getExifDate(Directory exifDirectory, int tagName) throws MetadataException {
String dateStr = (String)exifDirectory.getObject(tagName);
if (" : : : : ".equals(dateStr)) {
return null;
}
return exifDirectory.getDate(tagName);
}
String dateStr = (String)exifDirectory.getObject(tagName);
if (" : : : : ".equals(dateStr)) {
return null;
}
return exifDirectory.getDate(tagName);
}
Dimension decodeImageForDimensions(File file) throws IOException {
Dimension decodeImageForDimensions(File file) throws IOException {
String suffix = null;
String name = file.getName();
@ -278,10 +286,10 @@ public class DirectoryEntry extends Entry {
return new Dimension(img.getWidth(), img.getHeight());
}
private void sort() {
private void sort() {
Collections.sort(children, sort);
}
Collections.sort(children, sort);
}
void fillLinkedList() {
@ -305,19 +313,27 @@ public class DirectoryEntry extends Entry {
String caption = props.getProperty("caption");
setCaption(caption);
sort = ComparatorFactory.getSort(props.getProperty("sort"));
HashMap<String, Entry> entryMap = new HashMap<String, Entry>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
Iterator i = props.keySet().iterator();
Date oldest = new Date(file.lastModified());
Iterator<Object> 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);
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")));
Date fileDate = sdf.parse(props.getProperty("file." + name + ".captureDate"));
if (fileDate.before(oldest)) {
oldest = fileDate;
}
entry.setDate(fileDate);
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"));
@ -331,16 +347,29 @@ public class DirectoryEntry extends Entry {
}
}
} else if (key.startsWith("dir.") && !key.endsWith(".hidden")) {
String name = key.substring("dir.".length());
String name = key.substring("dir.".length());
boolean hidden = Boolean.parseBoolean(props.getProperty("dir." + name + ".hidden"));
if (!hidden) {
children.add(new DirectoryEntry(this, new File(file, name)));
DirectoryEntry dir = new DirectoryEntry(this, new File(file, name));
children.add(dir);
Date fileDate = dir.getEarliest();
if (fileDate != null && fileDate.before(oldest)) {
oldest = fileDate;
}
}
}
}
this.earliest = oldest;
if (thumbnail == null && !children.isEmpty()) {
setThumbnail(children.get(0).getThumbnail());
}
}
@Override
public Date getEarliest() {
loadChildren();
return earliest;
}
}

View file

@ -100,4 +100,8 @@ public abstract class Entry {
public File getPath() {
return file;
}
public Date getEarliest() {
return getDate();
}
}

View file

@ -2,6 +2,8 @@ package org.forkalsrud.album.web;
import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeoutException;
import javax.servlet.RequestDispatcher;
@ -230,7 +232,6 @@ public class AlbumServlet
public class Mapper {
public String map(File file) {
StringBuilder buf = new StringBuilder();
return appendFile(buf, file).toString();
}
@ -245,7 +246,15 @@ public class AlbumServlet
return appendFile(buf, file.getParentFile()).append('/').append(file.getName());
}
}
Calendar cal = Calendar.getInstance();
public String year(Date d) {
cal.setTime(d);
return String.valueOf(cal.get(Calendar.YEAR));
}
}
}
// eof