package org.simantics.fileimport.dropins; import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; import static java.nio.file.StandardWatchEventKinds.OVERFLOW; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.file.FileSystem; import java.nio.file.FileSystemException; 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.WatchEvent.Kind; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import org.simantics.fileimport.Activator; import org.simantics.fileimport.FileImportService; /** * Directory watcher based on {@link java.nio.file.WatchService} which will listen to file changes inside the dropins directory * ~/workspace/.metadata/plugins/org.simantics.fileimport/dropins * * @author Jani Simomaa * */ public class FileImportDropins { private static Thread watcherThread = null; private static DropinsFolderWatcher watcher = null; /** * Start watching the dropins folder which are located in * ~/workspace/.metadata/plugins/org.simantics.fileimport/dropins */ public static void watchDropinsFolder() { if (watcher == null && watcherThread == null) { try { watcher = new DropinsFolderWatcher(Activator.getDropinsFolder()); watcherThread = new Thread(watcher, "Simantics Dropins Folder watcher thread"); watcherThread.setDaemon(true); watcherThread.start(); } catch (IOException e) { e.printStackTrace(); } } } /** * Stop watching the dropins folder */ public static void unwatchDropinsFolder() { watcher.stop(); try { watcherThread.join(500); if (watcherThread.isAlive()) watcherThread.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } watcherThread = null; watcher = null; } private static class DropinsFolderWatcher implements Runnable { private final Path dropinsFolder; private final WatchService ws; private final AtomicBoolean stopped = new AtomicBoolean(true); private final Map keys = new HashMap<>(); public DropinsFolderWatcher(Path dropinsFolder) throws IOException { this.dropinsFolder = dropinsFolder; FileSystem fs = dropinsFolder.getFileSystem(); this.ws = fs.newWatchService(); registerAll(this.dropinsFolder); } private static void syncPath(Path f) throws IOException { // Does not seem to need 's' according to unit test in Windows boolean synced = false; int count = 0; while (!synced) { try (RandomAccessFile raf = new RandomAccessFile(f.toFile(), "rw")) { raf.getFD().sync(); synced = true; } catch (IOException e) { if (count == 3) { throw e; } else { try { Thread.sleep(50); } catch (InterruptedException e1) { e1.printStackTrace(); } count++; } } } } @Override public void run() { stopped.set(false); while (!stopped.get()) { try { WatchKey key = ws.take(); for (WatchEvent watchEvent : key.pollEvents()) { if (OVERFLOW == watchEvent.kind()) continue; // loop @SuppressWarnings("unchecked") WatchEvent pathEvent = (WatchEvent) watchEvent; Kind kind = pathEvent.kind(); Path parent = keys.get(key); Path newPath = parent.resolve(pathEvent.context()); if (FileImportService.DB_FILE.equals(newPath.getFileName().toString())) continue; if (ENTRY_CREATE == kind) { System.out.println("New path created: " + newPath); int current = 0; while (!Files.isWritable(newPath) && current <= 10) { System.out.println("Sleeping for file import (current=" + current +")"); Thread.sleep(200); current++; } FileImportService.performFileImport(newPath, Optional.of(t -> { if (t instanceof FileSystemException) { try { syncPath(newPath); } catch (IOException e) { e.printStackTrace(); } FileImportService.performFileImport(newPath, Optional.empty()); } else { t.printStackTrace(); } })); register(newPath); } else if (ENTRY_MODIFY == kind) { System.out.println("New path modified: " + newPath); } else if (ENTRY_DELETE == kind) { System.out.println("New path deleted: " + newPath); FileImportService.removeResourceForFile(newPath.toAbsolutePath(), Optional.empty()); } } if (!key.reset()) { keys.remove(key); // break; // loop } } catch (InterruptedException e) { if (!stopped.get()) e.printStackTrace(); } catch (Throwable t) { t.printStackTrace(); } } } public void stop() { stopped.set(true); } private void registerAll(Path path) throws IOException { Files.walkFileTree(path, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException { register(file); return FileVisitResult.CONTINUE; } }); } private void register(Path path) throws IOException { if (Files.isDirectory(path)) { WatchKey key = path.toAbsolutePath().register(ws, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); keys.put(key, path); } } } }