package de.eisfeldj.augendiagnosefx.util.imagefile;
import java.util.HashMap;
import de.eisfeldj.augendiagnosefx.controller.MainController;
import de.eisfeldj.augendiagnosefx.util.DialogUtil;
import de.eisfeldj.augendiagnosefx.util.Logger;
import de.eisfeldj.augendiagnosefx.util.ResourceConstants;
import javafx.application.Platform;
/**
* Utility class to help storing metadata in jpg files in a synchronized way, preventing to store the same file twice in
* parallel.
*/
public final class JpegSynchronizationUtil {
/**
* Hide default constructor.
*/
private JpegSynchronizationUtil() {
throw new UnsupportedOperationException();
}
/**
* Storage for currently running save tasks.
*/
private static HashMap<String, JpegMetadata> mRunningSaveRequests = new HashMap<>();
/**
* Storage for queued save tasks.
*/
private static HashMap<String, JpegMetadata> mQueuedSaveRequests = new HashMap<>();
/**
* This method handles a request to retrieve metadata for a file. If there is no running async task to update
* metadata for this file, then the data is taken directly from the file. Otherwise, it is taken from the last
* metadata to be stored for this file.
*
* @param pathname
* the path of the jpg file.
* @return null for non-JPEG files. The metadata from the file if readable. Otherwise empty metadata.
*/
public static JpegMetadata getJpegMetadata(final String pathname) {
JpegMetadata cachedMetadata = null;
try {
JpegMetadataUtil.checkJpeg(pathname);
}
catch (Exception e) {
Logger.warning(e.getMessage());
return null;
}
synchronized (JpegSynchronizationUtil.class) {
if (mQueuedSaveRequests.containsKey(pathname)) {
cachedMetadata = mQueuedSaveRequests.get(pathname);
}
else if (mRunningSaveRequests.containsKey(pathname)) {
cachedMetadata = mRunningSaveRequests.get(pathname);
}
}
if (cachedMetadata != null) {
Logger.info("Retrieve cached metadata for file " + pathname);
return cachedMetadata;
}
else {
try {
return JpegMetadataUtil.getMetadata(pathname);
}
catch (Exception e) {
Logger.error("Failed to retrieve metadata for file " + pathname, e);
return new JpegMetadata();
}
}
}
/**
* This method handles a request to update metadata on a file. If no such request on the file is in process, then an
* async task is started to update the metadata. Otherwise, it is put on the queue.
*
* @param pathname
* the path of the jpg file.
* @param metadata
* the metadata.
*/
public static void storeJpegMetadata(final String pathname, final JpegMetadata metadata) {
try {
JpegMetadataUtil.checkJpeg(pathname);
}
catch (Exception e) {
Logger.warning(e.getMessage());
return;
}
synchronized (JpegSynchronizationUtil.class) {
MainController.setSaveIconVisibility(true);
if (mRunningSaveRequests.containsKey(pathname)) {
mQueuedSaveRequests.put(pathname, metadata);
}
else {
triggerJpegSaverTask(pathname, metadata);
}
}
}
/**
* Get information if there is a running or pending save request.
*
* @return true if there is a running or pending save request.
*/
public static boolean hasRunningSaveRequests() {
return mRunningSaveRequests.size() > 0 || mQueuedSaveRequests.size() > 0;
}
/**
* Do cleanup from the last JpegSaverThread and trigger the next task on the same file, if existing.
*
* @param pathname
* The path of the jpg file.
*/
private static void triggerNextFromQueue(final String pathname) {
synchronized (JpegSynchronizationUtil.class) {
mRunningSaveRequests.remove(pathname);
if (mQueuedSaveRequests.containsKey(pathname)) {
Logger.info("Executing queued store request for file " + pathname);
JpegMetadata newMetadata = mQueuedSaveRequests.get(pathname);
mQueuedSaveRequests.remove(pathname);
triggerJpegSaverTask(pathname, newMetadata);
}
if (!hasRunningSaveRequests()) {
Platform.runLater(new Runnable() {
@Override
public void run() {
MainController.setSaveIconVisibility(false);
}
});
}
}
}
/**
* Utility method to start the JpegSaverThread so save a jpg file with metadata.
*
* @param pathname
* the path of the jpg file.
* @param metadata
* the metadata.
*/
private static void triggerJpegSaverTask(final String pathname, final JpegMetadata metadata) {
mRunningSaveRequests.put(pathname, metadata);
JpegSaverThread thread = new JpegSaverThread(pathname, metadata);
thread.start();
}
/**
* Thread to save a JPEG file asynchronously with changed metadata.
*/
private static final class JpegSaverThread extends Thread {
/**
* The path of the jpg file.
*/
private String mPathname;
/**
* The changed metadata.
*/
private JpegMetadata mMetadata;
/**
* Constructor for the task.
*
* @param pathname
* the path of the jpg file.
* @param metadata
* the metadata.
*/
private JpegSaverThread(final String pathname, final JpegMetadata metadata) {
this.mPathname = pathname;
this.mMetadata = metadata;
}
@Override
public void run() {
Logger.info("Starting thread to save file " + mPathname);
try {
JpegMetadataUtil.changeMetadata(mPathname, mMetadata);
Logger.info("Successfully saved file " + mPathname);
}
catch (Exception e) {
Logger.error("Failed to save file " + mPathname, e);
DialogUtil.displayError(ResourceConstants.MESSAGE_ERROR_FAILED_TO_STORE_METADATA, mPathname);
}
triggerNextFromQueue(mPathname);
}
}
}