package org.peerbox.filerecovery; import java.nio.file.Files; import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import org.hive2hive.core.model.IFileVersion; import org.hive2hive.core.processes.files.recover.IVersionSelector; /** * File version selector for H2H. * It works as follows: * - Available versions are received from H2H ({@link #selectVersion(List)}). * - This triggers a notification of the listener * (forward available versions, {@link IFileVersionSelectorListener#onAvailableVersionsReceived(List)}. * - The thread is blocked in {@link #selectVersion(List)} until a version is selected. * - The version selector waits using the count down latch until the listener chooses a version by * calling {@link #selectVersion(IFileVersion, Path)}. * - The selected version is returned (unblock, {@link #selectVersion(List)} continues). * * @author albrecht * */ final class FileVersionSelector implements IVersionSelector { private final CountDownLatch doneSignal; private final IFileVersionSelectorListener listener; private IFileVersion selectedVersion; private String recoveredFileName; private final AtomicBoolean gotAvailableVersions = new AtomicBoolean(); private final AtomicBoolean isCancelled = new AtomicBoolean(); private final AtomicBoolean hasSelected = new AtomicBoolean(); private Path fileToRecover; public FileVersionSelector(final IFileVersionSelectorListener listener) { if(listener == null) { throw new IllegalArgumentException("Argument listener must not be null."); } this.doneSignal = new CountDownLatch(1); this.gotAvailableVersions.set(false); this.isCancelled.set(false); this.hasSelected.set(false); this.recoveredFileName = ""; this.listener = listener; } /** * Cancel version selection. * Unblocks waiting thread in {@link #selectVersion(List)} (will return null). */ public void cancel() { isCancelled.set(true); selectedVersion = null; doneSignal.countDown(); } public boolean isCancelled() { return isCancelled.get(); } /** * Select a specific version of a file. * Unblocks the waiting thread in {@link #selectVersion(List)}. * * @param selectedVersion the version to recover * @param fileToRecover the path to the file to recover (original, not the new name!) */ public void selectVersion(IFileVersion selectedVersion, Path fileToRecover) { this.fileToRecover = fileToRecover; try { if(hasSelected.get()) { throw new IllegalStateException("Calling selectVersion multiple times is not allowed."); } hasSelected.set(true); if(!gotAvailableVersions.get()) { throw new IllegalStateException("Cannot select version before retrieving available versions."); } if(selectedVersion == null) { cancel(); } this.selectedVersion = selectedVersion; } finally { doneSignal.countDown(); // wake up other waiting thread } } /** * This is called by H2H - receive available versions. * Notifies listener and blocks until version is selected and returns version. */ @Override public IFileVersion selectVersion(List<IFileVersion> availableVersions) { if(isCancelled.get()) { // not interested in versions anymore -> select nothing (i.e. cancel) return null; } try { if(availableVersions != null) { gotAvailableVersions.set(true); listener.onAvailableVersionsReceived(availableVersions); // wait here until selectVersion(version) is called doneSignal.await(); } } catch (InterruptedException e) { // happens if e.g. file recovery is cancelled after retrieving available versions e.printStackTrace(); } return selectedVersion; } public String getRecoveredFileName() { return recoveredFileName; } @Override public String getRecoveredFileName(String fullName, String name, String extension) { if(isCancelled.get()) { return null; } // generate a new file name indicating that the file is restored Date versionDate = new Date(selectedVersion.getDate()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss"); /* * Search new name for file and make sure that we do not overwrite an existing file: * 1. try pattern oldfilename-date.extension * 2. if file already exists, extend pattern to: oldfilename-date-i.extension where i is * a counter until a file is found that does not exist. */ String newFileName = null; String postfix = ""; // set this postfix if file already exists int iteration = 0; do { if(iteration > 0) { // file seems to exists - add "-x" to the filename" postfix = String.format("-%d", iteration); } newFileName = String.format("%s-%s%s", name, sdf.format(versionDate), postfix); if(extension!=null && extension.length() > 0) { newFileName = String.format("%s.%s", newFileName, extension); } ++iteration; } while(Files.exists(fileToRecover.getParent().resolve(newFileName))); recoveredFileName = newFileName; return newFileName; } }