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); } } }