/*******************************************************************************
* JNotify - Allow java applications to register to File system events.
*
* Copyright (C) 2005 - Content Objects
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
******************************************************************************
*
* Content Objects, Inc., hereby disclaims all copyright interest in the
* library `JNotify' (a Java library for file system events).
*
* Yahali Sherman, 21 November 2005
* Content Objects, VP R&D.
*
******************************************************************************
* Author : Omry Yadan
******************************************************************************
* Edited: 12 September 2011 - Andrea Agili
******************************************************************************/
package net.contentobjects.jnotify.linux;
import java.io.File;
import java.util.ArrayList;
import java.util.Hashtable;
import net.contentobjects.jnotify.IJNotify;
import net.contentobjects.jnotify.JNotify;
import net.contentobjects.jnotify.JNotifyException;
import net.contentobjects.jnotify.JNotifyListener;
import net.contentobjects.jnotify.Util;
/** TODO : added by omry at Dec 11, 2005 : Handle move events */
public class JNotifyAdapterLinux implements IJNotify {
private static class WatchData {
boolean _user;
int _wd;
private int _linuxWd;
private ArrayList<Integer> _subWd;
int _mask;
int _linuxMask;
boolean _watchSubtree;
JNotifyListener _listener;
/*
* if the cookie hashtable is static every WatchData can access the
* cookies from other WatchData (directories). The static key word wont
* be a problem because: i) a cookie is deleted right after its retrival
* ii) cookies are only used with rename actions
*
* BUG: discovered by Fabio Bernasconi
*/
static Hashtable<Integer, String> _cookieToOldName = new Hashtable<Integer, String>();
String _path;
WatchData _parentWatchData;
WatchData(WatchData parentWatchData, boolean user, String path, int wd,
int linuxWd, int mask, int linuxMask, boolean watchSubtree,
JNotifyListener listener) {
if (listener == null) {
throw new IllegalArgumentException("Null listener");
}
this._parentWatchData = parentWatchData;
this._user = user;
this._subWd = new ArrayList<Integer>();
this._path = path;
this._wd = wd;
this._linuxMask = linuxMask;
this._linuxWd = linuxWd;
this._mask = mask;
this._watchSubtree = watchSubtree;
this._listener = listener;
if (parentWatchData != null) {
parentWatchData.addSubwatch(this._linuxWd);
}
}
void addSubwatch(int linuxWd) {
this._subWd.add(Integer.valueOf(linuxWd));
}
private String getOutName(String name) {
String outName;
if (this._user) {
outName = name;
} else // auto watch.
{
outName = this._path.substring(this.getParentWatch()._path
.length() + 1);
if (name != "") {
outName += File.separatorChar + name;
}
}
return outName;
}
private String getOutRoot() {
String outRoot;
if (this._user) {
outRoot = this._path;
} else // auto watch.
{
outRoot = this.getParentWatch()._path;
}
return outRoot;
}
public WatchData getParentWatch() {
return this._user ? this : this._parentWatchData;
}
public int getParentWatchID() {
return this._parentWatchData == null ? this._wd
: this._parentWatchData._wd;
}
public void notifyFileCreated(String name) {
String outRoot = this.getOutRoot();
String outName = this.getOutName(name);
this._listener.fileCreated(this.getParentWatchID(), outRoot,
outName);
}
public void notifyFileDeleted(String name) {
String outRoot = this.getOutRoot();
String outName = this.getOutName(name);
this._listener.fileDeleted(this.getParentWatchID(), outRoot,
outName);
}
public void notifyFileModified(String name) {
String outRoot = this.getOutRoot();
String outName = this.getOutName(name);
this._listener.fileModified(this.getParentWatchID(), outRoot,
outName);
}
public void notifyFileRenamed(String name, int cookie) {
String oldName = WatchData._cookieToOldName.remove(Integer
.valueOf(cookie));
String outRoot = this.getOutRoot();
String outNewName = this.getOutName(name);
this._listener.fileRenamed(this.getParentWatchID(), outRoot,
oldName, outNewName);
}
public void removeFromParent() {
if (this._parentWatchData == null) {
throw new RuntimeException("no parent");
}
this._parentWatchData.remveSubwatch(this._linuxWd);
}
void remveSubwatch(int linuxWd) {
if (!this._subWd.remove(Integer.valueOf(linuxWd))) {
throw new RuntimeException("Error removing " + linuxWd
+ " from list");
}
}
public void renaming(int cookie, String name) {
WatchData._cookieToOldName.put(Integer.valueOf(cookie),
this.getOutName(name));
String outRoot = this.getOutRoot();
this._listener.fileRenamed(this.getParentWatchID(), outRoot, name,
null);
}
public String toString() {
return "WatchData " + this._path + ", wd=" + this._wd
+ ", linuxWd=" + this._linuxWd
+ (this._watchSubtree ? ", recursive" : "")
+ (this._user ? ", user" : ", auto");
}
}
private Hashtable<Integer, Integer> _linuxWd2Wd;
private Hashtable<Integer, WatchData> _id2Data;
/**
* A set of files which was added by registerToSubTree (auto-watches)
*/
private Hashtable<String, String> _autoWatchesPaths;
private static int _watchIDCounter = 0;
public JNotifyAdapterLinux() {
JNotify_linux.setNotifyListener(new INotifyListener() {
public void notify(String name, int wd, int mask, int cookie) {
try {
JNotifyAdapterLinux.this.notifyChangeEvent(name, wd, mask,
cookie);
} catch (RuntimeException e) {
e.printStackTrace(System.out);
}
}
});
this._id2Data = new Hashtable<Integer, WatchData>();
this._linuxWd2Wd = new Hashtable<Integer, Integer>();
this._autoWatchesPaths = new Hashtable<String, String>();
}
public int addWatch(String path, int mask, boolean watchSubtree,
JNotifyListener listener) throws JNotifyException {
JNotify_linux.debug("JNotifyAdapterLinux.addWatch(path=" + path
+ ",mask=" + Util.getMaskDesc(mask) + ", watchSubtree="
+ watchSubtree + ")");
// map mask to linux inotify mask.
int linuxMask = 0;
if ((mask & JNotify.FILE_CREATED) != 0) {
linuxMask |= JNotify_linux.IN_CREATE;
}
if ((mask & JNotify.FILE_DELETED) != 0) {
linuxMask |= JNotify_linux.IN_DELETE;
linuxMask |= JNotify_linux.IN_DELETE_SELF;
}
if ((mask & JNotify.FILE_MODIFIED) != 0) {
linuxMask |= JNotify_linux.IN_ATTRIB;
linuxMask |= JNotify_linux.IN_MODIFY;
}
if ((mask & JNotify.FILE_RENAMED) != 0) {
linuxMask |= JNotify_linux.IN_MOVED_FROM;
linuxMask |= JNotify_linux.IN_MOVED_TO;
}
// if watching subdirs, listen on create anyway.
// to know when new sub directories are created.
// these events should not reach the client code.
if (watchSubtree) {
linuxMask |= JNotify_linux.IN_CREATE;
}
WatchData watchData = this.createWatch(null, true, new File(path),
mask, linuxMask, watchSubtree, listener);
if (watchSubtree) {
try {
File file = new File(path);
this.registerToSubTree(true, watchData, file, false);
} catch (JNotifyException e) {
// cleanup
this.removeWatch(watchData._wd);
// and throw.
throw e;
}
}
return watchData._wd;
}
private WatchData createWatch(WatchData parentWatchData, boolean user,
File path, int mask, int linuxMask, boolean watchSubtree,
JNotifyListener listener) throws JNotifyException {
String absPath = path.getPath();
int wd = JNotifyAdapterLinux._watchIDCounter++;
int linuxWd = JNotify_linux.addWatch(absPath, linuxMask);
WatchData watchData = new WatchData(parentWatchData, user, absPath, wd,
linuxWd, mask, linuxMask, watchSubtree, listener);
this._linuxWd2Wd.put(Integer.valueOf(linuxWd), Integer.valueOf(wd));
this._id2Data.put(Integer.valueOf(wd), watchData);
if (!user) {
this._autoWatchesPaths.put(absPath, absPath);
}
return watchData;
}
private void debugLinux(String name, int linuxWd, int linuxMask, int cookie) {
boolean IN_ACCESS = (linuxMask & JNotify_linux.IN_ACCESS) != 0;
boolean IN_MODIFY = (linuxMask & JNotify_linux.IN_MODIFY) != 0;
boolean IN_ATTRIB = (linuxMask & JNotify_linux.IN_ATTRIB) != 0;
boolean IN_CLOSE_WRITE = (linuxMask & JNotify_linux.IN_CLOSE_WRITE) != 0;
boolean IN_CLOSE_NOWRITE = (linuxMask & JNotify_linux.IN_CLOSE_NOWRITE) != 0;
boolean IN_OPEN = (linuxMask & JNotify_linux.IN_OPEN) != 0;
boolean IN_MOVED_FROM = (linuxMask & JNotify_linux.IN_MOVED_FROM) != 0;
boolean IN_MOVED_TO = (linuxMask & JNotify_linux.IN_MOVED_TO) != 0;
boolean IN_CREATE = (linuxMask & JNotify_linux.IN_CREATE) != 0;
boolean IN_DELETE = (linuxMask & JNotify_linux.IN_DELETE) != 0;
boolean IN_DELETE_SELF = (linuxMask & JNotify_linux.IN_DELETE_SELF) != 0;
boolean IN_MOVE_SELF = (linuxMask & JNotify_linux.IN_MOVE_SELF) != 0;
boolean IN_UNMOUNT = (linuxMask & JNotify_linux.IN_UNMOUNT) != 0;
boolean IN_Q_OVERFLOW = (linuxMask & JNotify_linux.IN_Q_OVERFLOW) != 0;
boolean IN_IGNORED = (linuxMask & JNotify_linux.IN_IGNORED) != 0;
String s = "";
if (IN_ACCESS) {
s += "IN_ACCESS, ";
}
if (IN_MODIFY) {
s += "IN_MODIFY, ";
}
if (IN_ATTRIB) {
s += "IN_ATTRIB, ";
}
if (IN_CLOSE_WRITE) {
s += "IN_CLOSE_WRITE, ";
}
if (IN_CLOSE_NOWRITE) {
s += "IN_CLOSE_NOWRITE, ";
}
if (IN_OPEN) {
s += "IN_OPEN, ";
}
if (IN_MOVED_FROM) {
s += "IN_MOVED_FROM, ";
}
if (IN_MOVED_TO) {
s += "IN_MOVED_TO, ";
}
if (IN_CREATE) {
s += "IN_CREATE, ";
}
if (IN_DELETE) {
s += "IN_DELETE, ";
}
if (IN_DELETE_SELF) {
s += "IN_DELETE_SELF, ";
}
if (IN_MOVE_SELF) {
s += "IN_MOVE_SELF, ";
}
if (IN_UNMOUNT) {
s += "IN_UNMOUNT, ";
}
if (IN_Q_OVERFLOW) {
s += "IN_Q_OVERFLOW, ";
}
if (IN_IGNORED) {
s += "IN_IGNORED, ";
}
int wd = this._linuxWd2Wd.get(Integer.valueOf(linuxWd)).intValue();
WatchData wdata = this._id2Data.get(Integer.valueOf(wd));
String path;
if (wdata != null) {
path = wdata._path;
if ((path != null) && (name != "")) {
path += File.separator + name;
}
} else {
path = name;
}
JNotify_linux.debug("Linux event : wd=" + linuxWd + " | " + s
+ " path: " + path + (cookie != 0 ? ", cookie=" + cookie : ""));
}
protected void notifyChangeEvent(String name, int linuxWd, int linuxMask,
int cookie) {
if (JNotify_linux.DEBUG) {
this.debugLinux(name, linuxWd, linuxMask, cookie);
}
synchronized (this._id2Data) {
Integer iwd = this._linuxWd2Wd.get(Integer.valueOf(linuxWd));
if (iwd == null) {
// This happens if an exception is thrown because used too many
// watches.
System.out
.println("JNotifyAdapterLinux: warning, recieved event for an unregisted LinuxWD "
+ linuxWd + " ignoring...");
return;
}
WatchData watchData = this._id2Data.get(iwd);
if (watchData != null) {
if ((linuxMask & JNotify_linux.IN_CREATE) != 0) {
File newRootFile = new File(watchData._path, name);
if (watchData._watchSubtree) {
try {
this.createWatch(watchData.getParentWatch(), false,
newRootFile, watchData._mask,
watchData._linuxMask,
watchData._watchSubtree,
watchData._listener);
// fire events for newly found directories under the
// new root.
WatchData parent = watchData.getParentWatch();
this.registerToSubTree(true, parent, newRootFile,
true);
} catch (JNotifyException e) {
// ignore missing files while registering subtree,
// may have already been deleted
if (e.getErrorCode() != JNotifyException.ERROR_NO_SUCH_FILE_OR_DIRECTORY) {
JNotify_linux
.warn("registerToSubTree : warning, failed to register "
+ newRootFile
+ " :"
+ e.getMessage()
+ " code = "
+ e.getErrorCode());
}
}
}
// make sure user really requested to be notified on this
// event.
// (in case of recursive listening, this IN_CREATE flag is
// always on, even if
// the user is not interester in creation events).
if ((watchData._mask & JNotify.FILE_CREATED) != 0) {
// fire an event only if the path is not in the
// path2Watch,
// meaning no watch has been created on it.
if (!this._autoWatchesPaths.contains(newRootFile
.getPath())) {
watchData.notifyFileCreated(name);
} else {
JNotify_linux
.debug("Assuming already sent event for "
+ newRootFile.getPath());
}
}
} else if ((linuxMask & JNotify_linux.IN_DELETE_SELF) != 0) {
watchData.notifyFileDeleted(name);
} else if ((linuxMask & JNotify_linux.IN_DELETE) != 0) {
watchData.notifyFileDeleted(name);
} else if (((linuxMask & JNotify_linux.IN_ATTRIB) != 0)
|| ((linuxMask & JNotify_linux.IN_MODIFY) != 0)) {
watchData.notifyFileModified(name);
} else if ((linuxMask & JNotify_linux.IN_MOVED_FROM) != 0) {
watchData.renaming(cookie, name);
} else if ((linuxMask & JNotify_linux.IN_MOVED_TO) != 0) {
watchData.notifyFileRenamed(name, cookie);
} else if ((linuxMask & JNotify_linux.IN_IGNORED) != 0) {
this._linuxWd2Wd
.remove(Integer.valueOf(watchData._linuxWd));
this._id2Data.remove(Integer.valueOf(watchData._wd));
if (!watchData._user) {
this._autoWatchesPaths.remove(watchData._path);
watchData.removeFromParent();
}
}
} else {
System.out
.println("JNotifyAdapterLinux: warning, recieved event for an unregisted WD "
+ iwd + ". ignoring...");
}
}
}
private void registerToSubTree(boolean isRoot, WatchData parentWatch,
File root, boolean fireCreatedEvents) throws JNotifyException {
if (!parentWatch._user) {
throw new RuntimeException("!parentWatch._user");
}
// make sure user really requested to be notified on this event.
// (in case of recursive listening, this IN_CREATE flag is always on,
// even if
// the user is not interester in creation events).
if (fireCreatedEvents
&& ((parentWatch._mask & JNotify.FILE_CREATED) != 0)) {
String name = root.toString().substring(
parentWatch._path.length() + 1);
parentWatch.notifyFileCreated(name);
}
if (root.isDirectory()) {
// root was already registered by the calling method.
if (!isRoot) {
try {
this.createWatch(parentWatch, false, root,
parentWatch._mask, parentWatch._linuxMask,
parentWatch._watchSubtree, parentWatch._listener);
} catch (JNotifyException e) {
if (e.getErrorCode() == JNotifyException.ERROR_WATCH_LIMIT_REACHED) {
JNotify_linux
.warn("JNotifyAdapterLinux.registerToSubTree : warning, failed to register "
+ root + " :" + e.getMessage());
}
{
throw e;
}
// else, on any other error, try subtree anyway..
}
}
String files[] = root.list();
if (files != null) {
for (int i = 0; i < files.length; i++) {
String file = files[i];
this.registerToSubTree(false, parentWatch, new File(root,
file), fireCreatedEvents);
}
}
}
}
public boolean removeWatch(int wd) throws JNotifyException {
JNotify_linux.debug("JNotifyAdapterLinux.removeWatch(" + wd + ")");
synchronized (this._id2Data) {
if (this._id2Data.containsKey(Integer.valueOf(wd))) {
WatchData watchData = this._id2Data.get(Integer.valueOf(wd));
this.unwatch(watchData);
return true;
} else {
return false;
}
}
}
public int unitTest_getNumWatches() {
return this._id2Data.size();
}
private void unwatch(WatchData data) throws JNotifyException {
JNotifyException ex = null;
boolean ok = true;
try {
JNotify_linux.removeWatch(data._linuxWd);
} catch (JNotifyException e) {
e.printStackTrace();
ex = e;
ok = false;
}
if (data._user) {
for (int i = 0; i < data._subWd.size(); i++) {
int wd = data._subWd.get(i).intValue();
try {
JNotify_linux.removeWatch(wd);
} catch (JNotifyException e) {
e.printStackTrace();
ex = e;
ok = false;
}
}
}
if (!ok) {
throw ex;
}
}
}