package com.github.pfichtner.jrunalyser.base.datasource;
import static com.google.common.base.Preconditions.checkState;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.pfichtner.jrunalyser.base.data.SegmentationUnit;
import com.github.pfichtner.jrunalyser.base.data.WayPoint;
import com.github.pfichtner.jrunalyser.base.data.stat.DefaultStatistics;
import com.github.pfichtner.jrunalyser.base.data.stat.Statistics;
import com.github.pfichtner.jrunalyser.base.data.track.DefaultTrack;
import com.github.pfichtner.jrunalyser.base.data.track.Id;
import com.github.pfichtner.jrunalyser.base.data.track.Track;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
public class SerializatingDatasourceFascade extends AbstractDatasourceFascade {
private static final Logger log = LoggerFactory
.getLogger(SerializatingDatasourceFascade.class);
private final DatasourceFascade delegate;
private final File cacheDir;
private Function<Id, Track> loadTrack = new Function<Id, Track>() {
@Override
public Track apply(Id id) {
try {
return loadTrack(id);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}
};
public SerializatingDatasourceFascade(File baseDir,
DatasourceFascade delegate) {
this.cacheDir = new File(baseDir, ".cache");
this.delegate = delegate;
checkState(this.cacheDir.exists() || this.cacheDir.mkdirs());
}
public Set<Id> getTrackIds() throws IOException {
return this.delegate.getTrackIds();
}
public Iterable<Id> getTrackIds(Date start, Date end) throws IOException {
return this.delegate.getTrackIds(start, end);
}
public Track loadTrack(Id id) throws IOException {
return cache(this.delegate.loadTrack(id));
}
@Override
public Iterable<Track> loadTracks(Iterable<Id> ids) throws IOException {
return Iterables.transform(ids, this.loadTrack);
}
public List<Id> listTracks(SegmentationUnit segmentationUnit)
throws IOException {
return this.delegate.listTracks(segmentationUnit);
}
public Optional<Statistics> loadBestSegment(Id id,
SegmentationUnit segmentationUnit) throws IOException {
Optional<Statistics> loadBestSegment = loadCachedBestSegment(id,
segmentationUnit);
if (!loadBestSegment.isPresent()) {
loadBestSegment = this.delegate.loadBestSegment(id,
segmentationUnit);
if (loadBestSegment.isPresent()) {
saveCachedBestSegment(id, segmentationUnit,
loadBestSegment.get());
}
}
return loadBestSegment;
}
public Set<Id> getSimilarTracks(final Id id) throws IOException {
return this.delegate.getSimilarTracks(id);
}
@Override
public boolean isAwayEqReturn(Id id) {
return this.delegate.isAwayEqReturn(id);
}
public Track addTrack(Track track) throws IOException {
return cache(this.delegate.addTrack(track));
}
public Track removeTrack(Id id) throws IOException {
return this.delegate.removeTrack(id);
}
@Override
public Set<WayPoint> getCommonWaypoints() {
return this.delegate.getCommonWaypoints();
}
// -------------------------------------------------------------
private Track cache(Track track) {
Statistics statistics = null;
if (track.getStatistics() == null) {
String retained = CharMatcher.JAVA_LETTER_OR_DIGIT
.retainFrom(String.valueOf(track.getId()));
File cache = new File(this.cacheDir, "trackstat_" + retained);
// TODO Add uptodatecheck for cache file
statistics = cache.exists() ? (Statistics) read(cache) : write(
cache, DefaultStatistics.ofTrack(track));
}
return new DefaultTrack(track.getId(), track.getMetadata(),
track.getWaypoints(), track.getSegments(), statistics);
}
private static <T> T write(File cache, T object) {
try {
ObjectOutputStream ois = new ObjectOutputStream(
new FileOutputStream(cache));
try {
ois.writeObject(object);
log.debug("Serialized {}", cache);
} finally {
ois.close();
}
} catch (FileNotFoundException e) {
throw Throwables.propagate(e);
} catch (IOException e) {
throw Throwables.propagate(e);
}
return object;
}
private static Object read(File cache) {
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
cache));
try {
return ois.readObject();
} finally {
log.debug("Deserialized {}", cache);
ois.close();
}
} catch (FileNotFoundException e) {
throw Throwables.propagate(e);
} catch (IOException e) {
throw Throwables.propagate(e);
} catch (ClassNotFoundException e) {
throw Throwables.propagate(e);
}
}
// ----------------------------------------------------------------------------
private Optional<Statistics> loadCachedBestSegment(Id id,
SegmentationUnit unit) {
File cache = createBestSegmentDataFile(id, unit);
return cache.exists() ? Optional.of((Statistics) read(cache))
: Optional.<Statistics> absent();
}
private Statistics saveCachedBestSegment(Id id, SegmentationUnit unit,
Statistics bestSegment) {
return write(createBestSegmentDataFile(id, unit), bestSegment);
}
private File createBestSegmentDataFile(Id id, SegmentationUnit unit) {
String retained = CharMatcher.JAVA_LETTER_OR_DIGIT.retainFrom(String
.valueOf(id) + unit);
File cache = new File(this.cacheDir, "segmentdata_" + retained);
return cache;
}
}