package photato.core.metadata;
import photato.core.metadata.exif.ExifMetadata;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.Closeable;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import photato.core.metadata.gps.Position;
import photato.core.metadata.gps.GpsCoordinatesHelper;
import photato.core.metadata.gps.IGpsCoordinatesDescriptionGetter;
import photato.core.metadata.exif.ExifToolParser;
import photato.helpers.FileHelper;
import photato.helpers.SerialisationGsonBuilder;
import photato.helpers.Tuple;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class MetadataAggregator implements IMetadataAggregator, Closeable {
private final FileSystem fileSystem;
private final String metadataCacheFilename;
private final IGpsCoordinatesDescriptionGetter coordinatesDescriptionGetter;
private final Timer timer;
private boolean hasNewInfos;
private final Map<String, Metadata> metadatas;
private final Object lock = new Object();
public MetadataAggregator(FileSystem fileSystem, String metadataCacheFilename, IGpsCoordinatesDescriptionGetter coordinatesDescriptionGetter) {
this.fileSystem = fileSystem;
this.metadataCacheFilename = metadataCacheFilename;
this.coordinatesDescriptionGetter = coordinatesDescriptionGetter;
this.metadatas = readFromCache(this.fileSystem, metadataCacheFilename);
this.timer = new Timer(false);
this.startAutoSave();
}
@Override
public Metadata getMetadata(Path path, long lastModifiedTimestamp) {
List<Tuple<Path, Long>> paths = new ArrayList<>();
paths.add(new Tuple<>(path, lastModifiedTimestamp));
return getMetadatas(paths).get(path);
}
@Override
public Map<Path, Metadata> getMetadatas(List<Tuple<Path, Long>> paths) {
Map<Path, Metadata> result = new HashMap<>();
// If in cache, returns cache info
synchronized (this.lock) {
List<Tuple<Path, Long>> remainingPaths = new ArrayList<>();
for (Tuple<Path, Long> pathTuple : paths) {
String key = getKey(pathTuple.o1, pathTuple.o2);
if (this.metadatas.containsKey(key)) {
result.put(pathTuple.o1, this.metadatas.get(key));
} else {
remainingPaths.add(pathTuple);
}
}
paths = remainingPaths;
}
Map<Path, ExifMetadata> exifToolParserResults = ExifToolParser.readMetadata(paths.stream().map((Tuple<Path, Long> t) -> t.o1).collect(Collectors.toList()));
for (Tuple<Path, Long> pathTuple : paths) {
ExifMetadata exifMetadata = exifToolParserResults.get(pathTuple.o1);
if (exifMetadata != null) {
Position position;
Tuple<Double, Double> coordinates = GpsCoordinatesHelper.getCoordinates(exifMetadata.getGPSPositionString());
String hardcodedPosition = exifMetadata.getHardcodedPosition();
if (this.coordinatesDescriptionGetter != null) {
position = new Position(coordinates.o1, coordinates.o2, hardcodedPosition, this.coordinatesDescriptionGetter.getCoordinatesDescription(coordinates.o1, coordinates.o2));
} else {
position = new Position(coordinates.o1, coordinates.o2, hardcodedPosition, null);
}
Metadata metadata = new Metadata(exifMetadata, position);
result.put(pathTuple.o1, metadata);
synchronized (lock) {
this.metadatas.put(getKey(pathTuple.o1, pathTuple.o2), metadata);
this.hasNewInfos = true;
}
}
}
return result;
}
@Override
public void close() throws IOException {
this.timer.cancel();
}
private void startAutoSave() {
this.timer.schedule(new TimerTask() {
@Override
public void run() {
synchronized (lock) {
if (hasNewInfos) {
hasNewInfos = false;
Gson g = new Gson();
try {
FileHelper.writeFile(fileSystem.getPath(metadataCacheFilename).toFile(), g.toJson(metadatas));
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}, 0, 20000);
}
private static Map<String, Metadata> readFromCache(FileSystem fileSystem, String metadataCacheFilename) {
try {
// Read metadata from cache
String data = FileHelper.readFile(fileSystem.getPath(metadataCacheFilename).toFile());
Map<String, Metadata> fromCacheFileMap = new Gson().fromJson(data, new TypeToken<Map<String, Metadata>>() {
}.getType());
// Remove outdated metadata
Map<String, Metadata> resultMap = new HashMap<>();
for (Map.Entry<String, Metadata> entry : fromCacheFileMap.entrySet()) {
try {
String filenameLastModified = entry.getKey();
Metadata value = entry.getValue();
String filename = filenameLastModified.split("\\?")[0];
long lastModifiedTimestamp = Long.parseLong(filenameLastModified.split("\\?")[1].split("\\.")[0]);
File f = fileSystem.getPath(filename).toFile();
if (f.exists() && f.lastModified() == lastModifiedTimestamp) {
resultMap.put(filenameLastModified, value);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
System.out.println("Read " + resultMap.size() + " metadata from cache!");
return resultMap;
} catch (IOException ex) {
System.err.println("Cannot read metadata from cache!");
return new HashMap<>();
}
}
private static String getKey(Path path, long lastModificationTimestamp) {
return path + "?" + lastModificationTimestamp;
}
}