blob: ef0b0272d06edcb2d2fe51e4fc2bc5f21fdb50f3 [file] [log] [blame]
package org.eclipse.osbp.xtext.signal.common;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
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.StandardWatchEventKinds;
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.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.osbp.xtext.signal.SignalHandlerTypeEnum;
import org.eclipse.osbp.xtext.signal.SignalTypeEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OSBPSignalWatcher {
private Logger log = LoggerFactory.getLogger("watcher");
/** the watcher service */
private WatchService watcher;
/** list of all created keys and paths */
private Map<WatchKey, Path> keys = null;
/** list of all signal type to watch */
private List<SignalTypeEnum> signals = null;
/**
* determines if the all tree of a directory has to be watched for changes
*/
private boolean recursive = false;
// private static final long TIMEOUT = 10L;
// private static final TimeUnit TIMEUNIT = TimeUnit.SECONDS;
/** the list of filemasks and the corresponding actions to execute */
private Map<String, SignalHandlerTypeEnum> masksAndactions = new HashMap<>();
public OSBPSignalWatcher() throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<WatchKey, Path>();
}
public void setSignals(List<SignalTypeEnum> signals) {
this.signals = signals;
}
/**
* Process all events for keys queued to the watcher
*/
public void processEvents() {
for (;;) {
// wait for key to be signaled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
log.error("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event : key.pollEvents()) {
Kind<?> kind = event.kind();
// handle the OVERFLOW event
if (kind == OVERFLOW) {
continue;
}
if (isHandlingAllowed(kind)) {
// Context for directory entry event is the file name of
// entry
WatchEvent<Path> ev = cast(event);
Path child = dir.resolve(ev.context());
log.debug(String.format("%s: %s\n", event.kind().name(), child));
// only allowed event can be handle
handleEvent(ev, child);
// if directory is created, and watching recursively, then
// register it and its sub-directories
if (recursive && (kind == ENTRY_CREATE)) {
try {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
registerAll(child);
}
} catch (IOException x) {
// ignore to keep sample readbale
}
}
}
// reset key and remove from set if directory no longer
// accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty()) {
break;
}
}
}
}
}
/**
* Checks if an event type has to be handle or not.
*
* @param event
* {@link WatchEvent<Path>} the event
* @return true if yes, false if not
*/
public boolean isHandlingAllowed(Kind<?> event) {
// if (signals.contains(SignalTypeEnum.ALL)) {
// return true;
// }
switch (event.name()) {
case "ENTRY_CREATE":
return signals.contains(SignalTypeEnum.CREATESIGNALS);
// case "ENTRY_DELETE":
// return signals.contains(SignalTypeEnum.DELETESIGNALS);
case "ENTRY_MODIFY":
return signals.contains(SignalTypeEnum.MODIFYSIGNALS);
default:
return false;
}
}
/**
* Handling method for the user to extend
*
* @param event
* the {@link WatchEvent}
*/
public void handleEvent(WatchEvent<Path> event, Path file) {
}
@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>) event;
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*
* @param rootpath
* {@link Path} the root path.
* @param signals
* @throws IOException
*/
public void registerAll(Path rootpath) throws IOException {
// register directory and sub-directories
Files.walkFileTree(rootpath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
registerPathToWatcher(dir);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Register the given directory with the WatchService
*
* @param directory
* @param signals
* @param combined
* true to create only one {@link WatchKey} for all event types,
* false to create separate key for each event type.
* @throws IOException
*/
public void registerPathToWatcher(Path directory) throws IOException {
if (signals != null && !signals.isEmpty() && directory != null) {
// if (signals.contains(SignalTypeEnum.ALL)) {
// // create a default key for all kind of changes
// // and save the key for later use
// getRegisteredKeysAndPaths().put(directory.register(getWatcher(), StandardWatchEventKinds.ENTRY_CREATE,
// StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY), directory);
// }
// else {
for (SignalTypeEnum signal : signals) {
switch (signal) {
case CREATESIGNALS: {
// create a default key for all kind of changes
// and save the key for later use
getRegisteredKeysAndPaths()
.put(directory.register(getWatcher(), StandardWatchEventKinds.ENTRY_CREATE), directory);
break;
}
case MODIFYSIGNALS: {
// create a default key for all kind of changes
// and save the key for later use
getRegisteredKeysAndPaths()
.put(directory.register(getWatcher(), StandardWatchEventKinds.ENTRY_MODIFY), directory);
break;
}
// case DELETESIGNALS: {
// // create a default key for all kind of changes
// // and save the key for later use
// getRegisteredKeysAndPaths()
// .put(directory.register(getWatcher(), StandardWatchEventKinds.ENTRY_DELETE), directory);
// break;
// }
default:
// create no keys
break;
}
}
// }
} else {
log.debug("directory or signal type missing");
}
}
/**
* Checks if the given {@link WatchKey} is valid.
*
* @return true if yes, false if not
*/
public boolean isKeyValid(WatchKey key) {
return key != null ? key.isValid() : false;
}
/**
*
* @return
*/
public int getWatcherState() {
return watcher != null ? 1 : -1;
}
/**
* Closes the watcher service
*
* @return true if yes or non existing, false if not
*/
public boolean closeWatcher() {
try {
if (watcher != null) {
watcher.close();
}
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* Get the list of registered directories.
*
* @return {@link Collection<Path>} {@link #directories}
*/
public Collection<Path> getRegisteredDirectories() {
return keys.values();
}
/**
* Gets the list of registered keys.
*
* @return {@link Set<WatchKey>}
*/
public Set<WatchKey> getRegisteredKeys() {
return keys.keySet();
}
/**
* Gets the list of registered keys and their paths.
*
* @return {@link Map<WatchKey,Path>}
*/
public Map<WatchKey, Path> getRegisteredKeysAndPaths() {
return keys;
}
/**
* Gets the watcher.
*
* @return {@link WatchService}
*/
public WatchService getWatcher() {
return watcher;
}
/**
* Provides the appropriate action to execute based on the filename/filemask
* check.
*
* @param filename
* filename
* @return
*/
public SignalHandlerTypeEnum checkFileMask(String filename) {
if (filename != null && !filename.isEmpty()) {
for (String mask : masksAndactions.keySet()) {
String filemask = mask.substring(mask.indexOf("|") + 1);
// checks if a file matches
if (filename.toLowerCase().equals(filemask.toLowerCase())
|| filename.toLowerCase().contains(filemask.toLowerCase())
|| filename.toLowerCase().matches(filemask.toLowerCase())) {
return masksAndactions.get(mask);
}
}
}
// in the case of no filename matches
return SignalHandlerTypeEnum.DOAFTER;
}
public String isFileMaskValid(String filename, Kind<?> event) {
if (filename != null && !filename.isEmpty()) {
// checks if a filemask exist with the corresponding action
// filemask + create | filemask + modify | filemask + delete
for (String mask : masksAndactions.keySet()) {
String filemask = mask.substring(mask.indexOf("|") + 1);
// check if filename matches the mask
if (filename.toLowerCase().equals(filemask.toLowerCase())
|| filename.toLowerCase().contains(filemask.toLowerCase())
|| filename.toLowerCase().matches(filemask.toLowerCase())) {
// then if the action is also allowed
if (masksAndactions.get(mask).equals(getAcionTypeOnEvent(event))) {
return mask;
}
}
}
}
// in the case of no filename matches
return null;
}
private SignalHandlerTypeEnum getAcionTypeOnEvent(Kind<?> event) {
switch (event.name()) {
case "ENTRY_CREATE":
return SignalHandlerTypeEnum.DOAFTERCREATE;
case "ENTRY_MODIFY":
return SignalHandlerTypeEnum.DOAFTERMODIFY;
// case "ENTRY_DELETE":
// return SignalHandlerTypeEnum.DOAFTERDELETE;
default:
return SignalHandlerTypeEnum.DOAFTER;
}
}
public void addFilemaskAndAction(String filemask, SignalHandlerTypeEnum action) {
this.masksAndactions.put(filemask, action);
}
}