package org.jabref.logic.autosaveandbackup;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.database.event.AutosaveEvent;
import org.jabref.model.database.event.BibDatabaseContextChangedEvent;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Saves the given {@link BibDatabaseContext} on every {@link BibDatabaseContextChangedEvent} by posting a new {@link AutosaveEvent}.
* An intelligent {@link ExecutorService} with a {@link BlockingQueue} prevents a high load while saving and rejects all redundant save tasks.
*/
public class AutosaveManager {
private static final Log LOGGER = LogFactory.getLog(AutosaveManager.class);
private static Set<AutosaveManager> runningInstances = new HashSet<>();
private final BibDatabaseContext bibDatabaseContext;
private final BlockingQueue<Runnable> workerQueue;
private final ExecutorService executor;
private final EventBus eventBus;
private AutosaveManager(BibDatabaseContext bibDatabaseContext) {
this.bibDatabaseContext = bibDatabaseContext;
this.workerQueue = new ArrayBlockingQueue<>(1);
this.executor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, workerQueue);
this.eventBus = new EventBus();
}
@Subscribe
public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextChangedEvent event) {
try {
executor.submit(() -> {
eventBus.post(new AutosaveEvent());
});
} catch (RejectedExecutionException e) {
LOGGER.debug("Rejecting autosave while another save process is already running.");
}
}
private void shutdown() {
bibDatabaseContext.getDatabase().unregisterListener(this);
bibDatabaseContext.getMetaData().unregisterListener(this);
executor.shutdown();
}
/**
* Starts the Autosaver which is associated with the given {@link BibDatabaseContext}.
*
* @param bibDatabaseContext Associated {@link BibDatabaseContext}
*/
public static AutosaveManager start(BibDatabaseContext bibDatabaseContext) {
AutosaveManager autosaver = new AutosaveManager(bibDatabaseContext);
bibDatabaseContext.getDatabase().registerListener(autosaver);
bibDatabaseContext.getMetaData().registerListener(autosaver);
runningInstances.add(autosaver);
return autosaver;
}
/**
* Shuts down the Autosaver which is associated with the given {@link BibDatabaseContext}.
*
* @param bibDatabaseContext Associated {@link BibDatabaseContext}
*/
public static void shutdown(BibDatabaseContext bibDatabaseContext) {
runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).findAny()
.ifPresent(instance -> {
instance.shutdown();
runningInstances.remove(instance);
});
}
public void registerListener(Object listener) {
eventBus.register(listener);
}
public void unregisterListener(Object listener) {
try {
eventBus.unregister(listener);
} catch (IllegalArgumentException e) {
// occurs if the event source has not been registered, should not prevent shutdown
LOGGER.debug(e);
}
}
}