Commit 207317ee authored by Stefan Schuhbaeck's avatar Stefan Schuhbaeck
Browse files

Remove directory watcher and use postSimulation callback for new output

parent b9186630
......@@ -264,7 +264,7 @@ public class ProjectViewModel {
@Override
public void run() {
fireRefreshOutputStarted();
project.getProjectOutput().cleanOutputDirs();
project.getProjectOutput().update();
outputTableModel.init(project);
fireRefreshOutputCompleted();
}
......@@ -318,6 +318,10 @@ public class ProjectViewModel {
public Collection<File> getOutputDirectories() {
return outputDirectories;
}
public Scenario getScenarioRM(){
return project.getProjectOutput().getScenario(directory.getName());
}
}
public VTable createOutputTable() {
......
......@@ -522,8 +522,8 @@ public class ProjectView extends JFrame implements ProjectFinishedListener, Sing
}
private void loadScenarioIntoGui(OutputBundle bundle) throws IOException {
Scenario scenarioRM = IOOutput.readScenarioRunManager(bundle.getProject(),
bundle.getDirectory().getName());
Scenario scenarioRM = bundle.getScenarioRM();
Optional<File> optionalTrajectoryFile = IOUtils
.getFirstFile(bundle.getDirectory(), IOUtils.TRAJECTORY_FILE_EXTENSION);
if (optionalTrajectoryFile.isPresent()) {
......
package org.vadere.simulator.projects;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* Implements directory watcher to notify project if the filesystem changes.
*
* @author Stefan Schuhbäck
*/
public class OutputDirWatcher implements Runnable {
private WatchService watchService;
private ConcurrentHashMap<WatchKey, Path> keys;
private List<WatchEventHandler> handlers;
private VadereProject project;
public OutputDirWatcher(VadereProject project) throws IOException {
this.project = project;
}
@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>) event;
}
private WatchEvent<Path>[] getWatchEvents(List<WatchEvent<?>> list) {
List<WatchEvent<Path>> ret = new ArrayList<>();
list.forEach(e -> ret.add(cast(e)));
return list.toArray(new WatchEvent[list.size()]);
}
@Override
public void run() {
System.out.println("Start Watching...");
try {
while (true) {
WatchKey key = watchService.take();
Path dir = keys.get(key);
if (dir == null) {
System.out.println("Key not found: " + key.toString());
continue;
}
WatchEvent<Path>[] events = getWatchEvents(key.pollEvents());
handlers.forEach(handler -> handler.processEvent(dir, events));
boolean valid = key.reset();
if (!valid)
keys.remove(key);
}
} catch (InterruptedException e) {
try {
watchService.close();
System.out.println("Cleanup Watcher...");
} catch (IOException e1) {
//log
e1.printStackTrace();
}
System.out.println("return from Watcher");
return;
}
}
public WatchService getWatchService() {
return watchService;
}
public void setWatchService(WatchService watchService) {
this.watchService = watchService;
}
public ConcurrentHashMap<WatchKey, Path> getKeys() {
return keys;
}
public void setKeys(ConcurrentHashMap<WatchKey, Path> keys) {
this.keys = keys;
}
public List<WatchEventHandler> getHandlers() {
return handlers;
}
public void setHandlers(List<WatchEventHandler> handlers) {
this.handlers = handlers;
}
public VadereProject getProject() {
return project;
}
public void setProject(VadereProject project) {
this.project = project;
}
}
package org.vadere.simulator.projects;
import org.vadere.util.io.IOUtils;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
/**
* Builder class for {@link OutputDirWatcher}.
*
* @author Stefan Schuhbäck
*/
public class OutputDirWatcherBuilder {
public static final WatchEvent.Kind<?>[] DEFAULT_KEYS =
new WatchEvent.Kind<?>[]{ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY};
private OutputDirWatcher outputDirWatcher;
private ConcurrentHashMap<WatchKey, Path> keys;
private WatchService watchService;
private List<WatchEventHandler> handlers;
public OutputDirWatcherBuilder() {
}
public OutputDirWatcherBuilder initOutputDirWatcher(VadereProject project) throws IOException {
this.outputDirWatcher = new OutputDirWatcher(project);
this.watchService = FileSystems.getDefault().newWatchService();
this.keys = new ConcurrentHashMap<>();
this.handlers = new ArrayList<>();
return this;
}
public OutputDirWatcherBuilder registerDefaultDir() throws IOException {
Path root = outputDirWatcher.getProject().getOutputDir();
registerAll(root);
addDefaultEventHandler();
return this;
}
public OutputDirWatcher build() {
outputDirWatcher.setKeys(this.keys);
outputDirWatcher.setHandlers(this.handlers);
outputDirWatcher.setWatchService(this.watchService);
return outputDirWatcher;
}
public void addDefaultEventHandler() {
WatchEventHandler simulationOutputDirCreated = (dir, ev) -> {
if ((ev.length == 1) && (ev[0].kind() == ENTRY_CREATE)) {
Path context = dir.resolve(ev[0].context());
if (context.toFile().isDirectory()) {
System.out.println("simulationOutputDirCreated ...");
}
}
};
addEventHandler(simulationOutputDirCreated);
WatchEventHandler simulationOutputDirModified = (dir, ev) -> {
if ((ev.length == 2) && (ev[0].kind() == ENTRY_DELETE) && (ev[1].kind() == ENTRY_CREATE)) {
Path context = dir.resolve(ev[1].context());
if (context.toFile().isDirectory()) {
System.out.print("simulationOutputDirModified new name: ");
System.out.println(context.getFileName().toString());
}
}
};
addEventHandler(simulationOutputDirModified);
WatchEventHandler simulationOutputFileNew = (dir, ev) -> {
if ((ev.length == 2) && (ev[0].kind() == ENTRY_CREATE) && (ev[1].kind() == ENTRY_MODIFY)) {
Path context = dir.resolve(ev[1].context());
if (context.toFile().isFile()) {
System.out.print("a file was created...");
System.out.format(" in %s with the name of %s as dirty!%n", dir.getFileName().toString(), context.getFileName().toString());
}
}
};
addEventHandler(simulationOutputFileNew);
WatchEventHandler simulationOutputFileModified = (dir, ev) -> {
if ((ev.length == 1) && (ev[0].kind() == ENTRY_MODIFY)) {
Path context = dir.resolve(ev[0].context());
if (context.toFile().isFile()) {
System.out.print("a file was modified...");
System.out.format(" mark %s as dirty!%n", dir.getFileName().toString());
}
}
};
addEventHandler(simulationOutputFileModified);
WatchEventHandler delete = (dir, ev) -> {
if ((ev.length == 1) && (ev[0].kind() == ENTRY_DELETE)) {
Path context = dir.resolve(ev[0].context());
if (context.toFile().isFile()) {
System.out.format("file deleted %s!%n", context.getFileName().toString());
} else {
System.out.format("dir deleted %s!%n", context.getFileName().toString());
}
}
};
addEventHandler(delete);
}
public OutputDirWatcherBuilder addEventHandler(WatchEventHandler handler) {
this.handlers.add(handler);
return this;
}
public void register(List<Path> dirs, WatchEvent.Kind<?>... events) throws IOException {
for(Path dir : dirs){
register(dir, events);
}
}
public void register(Path dir, WatchEvent.Kind<?>... events) throws IOException {
WatchEvent.Kind<?>[] selectedEvents;
if (events.length <= 0) {
selectedEvents = DEFAULT_KEYS;
} else {
selectedEvents = events;
}
if (dir.toString().contains(IOUtils.CORRUPT_DIR))
return;
WatchKey key = dir.register(watchService, selectedEvents);
keys.put(key, dir);
}
public void registerAll(Path root, WatchEvent.Kind<?>... events) throws IOException {
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
//do not watch corrupt directory or its children
if (dir.endsWith(IOUtils.CORRUPT_DIR)) {
return FileVisitResult.SKIP_SUBTREE;
}
register(dir, events);
return FileVisitResult.CONTINUE;
}
});
}
public OutputDirWatcher getOutputDirWatcher() {
return outputDirWatcher;
}
public void setOutputDirWatcher(OutputDirWatcher outputDirWatcher) {
this.outputDirWatcher = outputDirWatcher;
}
public ConcurrentHashMap<WatchKey, Path> getKeys() {
return keys;
}
public void setKeys(ConcurrentHashMap<WatchKey, Path> keys) {
this.keys = keys;
}
public WatchService getWatchService() {
return watchService;
}
public void setWatchService(WatchService watchService) {
this.watchService = watchService;
}
public List<WatchEventHandler> getHandlers() {
return handlers;
}
public void setHandlers(List<WatchEventHandler> handlers) {
this.handlers = handlers;
}
}
......@@ -29,46 +29,33 @@ public class ProjectOutput {
private final VadereProject project;
private ConcurrentMap<String, SimulationOutput> simulationOutputs;
private OutputDirWatcher watcher;
public ProjectOutput(VadereProject project) {
public ProjectOutput(final VadereProject project) {
this.project = project;
this.simulationOutputs = IOOutput.getSimulationOutputs(project);
// add watched directories manually to ensure only valid output directories
// and the root output directory are added.
// TODO exceptions?
try {
OutputDirWatcherBuilder builder = new OutputDirWatcherBuilder();
builder.initOutputDirWatcher(project);
builder.register(project.getOutputDir());
builder.addDefaultEventHandler();
Path out = project.getOutputDir();
List<Path> outputs = simulationOutputs.keySet().stream()
.map(k -> out.resolve(k))
.collect(Collectors.toList());
builder.register(outputs);
this.watcher = builder.build();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Returns cached output directories. This function does not check if cached {@link SimulationOutput}
* is marked dirty. It is assumed {@link #update()} was called prior to this function.
*
* @return list of output directories corresponding to cached {@link SimulationOutput}
*/
public List<File> getAllOutputDirs() {
System.out.println("No FS Touch!");
Path out = project.getOutputDir();
// Keys of concurrentMap are the output directories for each simulation run.
// Resolve them against the project output dir gets you the needed paths/files.
List<File> outputs = simulationOutputs.keySet().stream()
.map(k -> out.resolve(k).toFile())
.collect(Collectors.toList());
return outputs;
}
/**
* This function does not check if cached {@link SimulationOutput}
* is dirty. It is assumed {@link #update()} was called prior to this function.
*
* @param scenario Prior runs to this {@link Scenario}
* @return List of prior simulation runs matching selected {@link Scenario}
*/
public List<File> listSelectedOutputDirs(final Scenario scenario) {
List<File> out = new ArrayList<>();
try {
......@@ -86,62 +73,61 @@ public class ProjectOutput {
return out;
}
synchronized public void reloadData() {
}
synchronized public void markDirty(String outputDir) {
Optional.ofNullable(this.simulationOutputs.get(outputDir)).ifPresent(SimulationOutput::setDirty);
}
public ConcurrentMap<String, SimulationOutput> getSimulationOutputs() {
return simulationOutputs;
}
public void setSimulationOutputs(ConcurrentMap<String, SimulationOutput> simulationOutputs) {
this.simulationOutputs = simulationOutputs;
/**
* If the output directory is present it is marked dirty and will be re-check
* in the next call of {@link #update()}
*
* @param dirName directory name of {@link SimulationOutput}
*/
synchronized public void markDirty(String dirName) {
getSimulationOutput(dirName).ifPresent(SimulationOutput::setDirty);
}
public Optional<SimulationOutput> getSimulationOutput(String dirName) {
return Optional.ofNullable(simulationOutputs.get(dirName));
}
public void cleanOutputDirs() {
Iterator<Map.Entry<String, SimulationOutput>> iter = this.simulationOutputs.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, SimulationOutput> entry = iter.next();
if (entry.getValue().isDirty()) {
Optional<SimulationOutput> updated =
IOOutput.getSimulationOutput(project, entry.getValue().getOutputDir());
if (updated.isPresent()) {
entry.setValue(updated.get());
} else {
//existing dir went invalid.
iter.remove();
}
}
}
public Scenario getScenario(String dirName){
return getSimulationOutput(dirName).get().getSimulatedScenario();
}
/**
* re-check dirty {@link SimulationOutput} and add new valid output dirs to {@link ProjectOutput}
*/
public void update() {
List<File> existingOutputDirs = getAllOutputDirs();
try {
Files.walkFileTree(project.getOutputDir(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
if (dir.endsWith(IOUtils.CORRUPT_DIR)) {
String dirName = dir.toFile().getName();
//ignore corrupt directory and the subtree
if (dir.endsWith(IOUtils.CORRUPT_DIR))
return FileVisitResult.SKIP_SUBTREE;
}
if (!existingOutputDirs.contains(dir.toFile())) {
//ignore output directory but continue with subtree
if (dir.endsWith(IOUtils.OUTPUT_DIR))
return FileVisitResult.CONTINUE;
Optional<SimulationOutput> outDir = getSimulationOutput(dirName);
// only re-check existing SimulationOutput if they are dirty
if (outDir.isPresent() && outDir.get().isDirty()) {
Optional<SimulationOutput> newSim = IOOutput.getSimulationOutput(project, dir.toFile());
if (newSim.isPresent()){
simulationOutputs.put(dirName, newSim.get());
} else {
simulationOutputs.remove(dirName);
}
} else {
// if new directory try to read it or move it corrupt if not valid.
Optional<SimulationOutput> newSim = IOOutput.getSimulationOutput(project, dir.toFile());
newSim.ifPresent(out -> simulationOutputs.put(dir.toFile().getName(), out));
}
return FileVisitResult.CONTINUE;
return FileVisitResult.SKIP_SUBTREE;
}
});
......
......@@ -44,15 +44,8 @@ public class SimulationOutput {
return isDirty;
}
public void setDirty(boolean dirty) {
isDirty = dirty;
}
public void setDirty() {
isDirty = true;
}
public File getOutputDir() {
return outputDirectory.toFile();
}
}
package org.vadere.simulator.projects;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
/**
*
* @author Stefan Schuhbäck
*/
@FunctionalInterface
public interface WatchEventHandler {
void processEvent(Path dir, WatchEvent<Path>[] events);
}
package org.vadere.simulator.projects;
import org.junit.Before;
import org.junit.Test;
import org.vadere.simulator.projects.io.IOVadere;
import org.vadere.util.io.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
/**
*
* @author Stefan Schuhbäck
*/
public class OutputDirWatcherBuilderTest {
private VadereProject project;
private OutputDirWatcherBuilder builder;
@Before
public void setup() throws URISyntaxException, IOException {
Path projectPath = Paths.get(getClass().getResource("/data/simpleProject").toURI());
project = IOVadere.readProject(projectPath.toString());
builder = new OutputDirWatcherBuilder();
}
@Test
public void initOutputDirWatcher() throws Exception {
builder.initOutputDirWatcher(project);