* added WatchService to Scripting API
This commit is contained in:
parent
9e942bc659
commit
fae09a653a
|
@ -0,0 +1,206 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.cli;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.nio.file.StandardWatchEventKinds.*;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
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.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.DefaultThreadFactory;
|
||||||
|
import net.sourceforge.tuned.Timer;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class FolderWatchService implements Closeable {
|
||||||
|
|
||||||
|
private final Collection<Path> commitSet = new HashSet<Path>();
|
||||||
|
|
||||||
|
private final ExecutorService processor = Executors.newSingleThreadExecutor();
|
||||||
|
private final ExecutorService watchers = Executors.newCachedThreadPool(new DefaultThreadFactory("FolderWatchService", Thread.MIN_PRIORITY, true));
|
||||||
|
|
||||||
|
private long commitDelay = 500; // 0.5 s
|
||||||
|
private final Timer commitTimer = new Timer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (processor) {
|
||||||
|
commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
public synchronized void setCommitDelay(long commitDelay) {
|
||||||
|
if (commitDelay < 0)
|
||||||
|
throw new IllegalArgumentException("Delay must not be negativ");
|
||||||
|
|
||||||
|
this.commitDelay = commitDelay;
|
||||||
|
resetCommitTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public synchronized void resetCommitTimer() {
|
||||||
|
commitTimer.set(commitDelay, TimeUnit.MILLISECONDS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public synchronized void commit() {
|
||||||
|
final SortedSet<File> files = new TreeSet<File>();
|
||||||
|
|
||||||
|
synchronized (commitSet) {
|
||||||
|
for (Path path : commitSet) {
|
||||||
|
files.add(path.toFile());
|
||||||
|
}
|
||||||
|
commitSet.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
processor.submit(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (processor) {
|
||||||
|
processCommitSet(files.toArray(new File[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract void processCommitSet(File[] files);
|
||||||
|
|
||||||
|
|
||||||
|
public synchronized void watch(File node) throws IOException {
|
||||||
|
if (!node.isDirectory()) {
|
||||||
|
throw new IllegalArgumentException("Must be a folder: " + node);
|
||||||
|
}
|
||||||
|
|
||||||
|
watchers.submit(new FolderWatcher(node.toPath()) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void processEvents(List<WatchEvent<?>> events) {
|
||||||
|
synchronized (commitSet) {
|
||||||
|
resetCommitTimer();
|
||||||
|
super.processEvents(events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void created(Path path) {
|
||||||
|
commitSet.add(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void modified(Path path) {
|
||||||
|
commitSet.add(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deleted(Path path) {
|
||||||
|
commitSet.remove(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void close() throws IOException {
|
||||||
|
commitTimer.cancel();
|
||||||
|
processor.shutdownNow();
|
||||||
|
watchers.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private abstract static class FolderWatcher implements Runnable, Closeable {
|
||||||
|
|
||||||
|
private final Path node;
|
||||||
|
private final WatchService watchService;
|
||||||
|
|
||||||
|
|
||||||
|
public FolderWatcher(Path node) throws IOException {
|
||||||
|
this.node = node;
|
||||||
|
this.watchService = node.getFileSystem().newWatchService();
|
||||||
|
node.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
watch();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// ignore, part of an orderly shutdown
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void watch() throws IOException, InterruptedException {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
WatchKey key = watchService.take();
|
||||||
|
processEvents(key.pollEvents());
|
||||||
|
key.reset();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Path getPath(WatchEvent event) {
|
||||||
|
return node.resolve(event.context().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void processEvents(List<WatchEvent<?>> list) {
|
||||||
|
for (WatchEvent event : list) {
|
||||||
|
if (event.kind() == ENTRY_CREATE) {
|
||||||
|
created(getPath(event));
|
||||||
|
} else if (event.kind() == ENTRY_MODIFY) {
|
||||||
|
modified(getPath(event));
|
||||||
|
} else if (event.kind() == ENTRY_DELETE) {
|
||||||
|
deleted(getPath(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract void created(Path path);
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract void modified(Path path);
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract void deleted(Path path);
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
watchService.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -67,6 +67,7 @@ class ScriptShell {
|
||||||
bindings.put(service.getName().toLowerCase(), PrivilegedInvocation.newProxy(MovieIdentificationService.class, service, acc));
|
bindings.put(service.getName().toLowerCase(), PrivilegedInvocation.newProxy(MovieIdentificationService.class, service, acc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindings.put("console", System.console());
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ File.metaClass.moveTo = { f -> renameFile(delegate, f) }
|
||||||
List.metaClass.mapByFolder = { mapByFolder(delegate) }
|
List.metaClass.mapByFolder = { mapByFolder(delegate) }
|
||||||
List.metaClass.mapByExtension = { mapByExtension(delegate) }
|
List.metaClass.mapByExtension = { mapByExtension(delegate) }
|
||||||
|
|
||||||
|
|
||||||
// Shell helper
|
// Shell helper
|
||||||
import static com.sun.jna.Platform.*;
|
import static com.sun.jna.Platform.*;
|
||||||
|
|
||||||
|
@ -62,6 +63,36 @@ def execute(String... args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// WatchService helper
|
||||||
|
import net.sourceforge.filebot.cli.FolderWatchService;
|
||||||
|
|
||||||
|
def getWatchService(Closure callback, List folders) {
|
||||||
|
// sanity check
|
||||||
|
folders.find{ if (!it.isDirectory()) throw new Exception("Must be a folder: " + it) }
|
||||||
|
|
||||||
|
// create watch service and setup callback
|
||||||
|
def watchService = new FolderWatchService() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
def void processCommitSet(File[] fileset) {
|
||||||
|
callback(fileset.toList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect updates for 5 minutes and then batch process
|
||||||
|
watchService.setCommitDelay(5 * 60 * 1000)
|
||||||
|
|
||||||
|
// start watching given files
|
||||||
|
folders.each { watchService.watch(it) }
|
||||||
|
|
||||||
|
return watchService
|
||||||
|
}
|
||||||
|
|
||||||
|
File.metaClass.watch = { c -> getWatchService(c, [delegate]) }
|
||||||
|
List.metaClass.watch = { c -> getWatchService(c, delegate) }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// CLI bindings
|
// CLI bindings
|
||||||
def rename(args) { args = _defaults(args)
|
def rename(args) { args = _defaults(args)
|
||||||
_guarded { _cli.rename(_files(args), args.query, args.format, args.db, args.lang, args.strict) }
|
_guarded { _cli.rename(_files(args), args.query, args.format, args.db, args.lang, args.strict) }
|
||||||
|
|
|
@ -20,7 +20,12 @@ def incomplete(f) { f =~ /[.]chunk|[.]part$/ }
|
||||||
|
|
||||||
// all volumes complete, call unrar on first volume
|
// all volumes complete, call unrar on first volume
|
||||||
if (incomplete.isEmpty()) {
|
if (incomplete.isEmpty()) {
|
||||||
execute("unrar", "x", "-y", "-p-", rarP1.getAbsolutePath(), rarP1.getPathWithoutExtension() + "/")
|
def exitCode = execute("unrar", "x", "-y", "-p-", rarP1.getAbsolutePath(), rarP1.getPathWithoutExtension() + "/")
|
||||||
|
|
||||||
|
// delete all volumes after successful extraction
|
||||||
|
if (exitCode == 0) {
|
||||||
|
volumes*.delete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
def dirs = args.getFolders()
|
||||||
|
|
||||||
|
// watch folders and print files that were added/modified (requires Java 7)
|
||||||
|
dirs.watch { println "Batch: " + it } // default commit delay is 5 minutes
|
||||||
|
dirs.watch { println "Quick: " + it }.setCommitDelay(100) // 100 ms commit delay
|
||||||
|
|
||||||
|
println "Waiting for events"
|
||||||
|
console.readLine()
|
Loading…
Reference in New Issue