Skip to content

Instantly share code, notes, and snippets.

@ryan-beckett
Created February 10, 2012 19:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save ryan-beckett/1792201 to your computer and use it in GitHub Desktop.
Save ryan-beckett/1792201 to your computer and use it in GitHub Desktop.
A facade for receiving notifcations for SMB file modifications through the JCIFS API. Depends on jcifs-1.3.17.jar.
/**
*
* Defines a default handler for notification callbacks.
*
* @author Ryan Beckett
* @version 1.0
*
*/
public abstract class NotificationHandler {
/**
* A callback for the addition of a file in the watched directory.
*
* @param file
* The relative file name of the newly created file.
*/
public abstract void handleNewFile(String file);
/**
* A callback for the deletion of a file in the watched directory.
*
* @param file
* The relative file name of the deleted file.
*/
public abstract void handleDeletedFile(String file);
}
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbException;
import jcifs.smb.SmbFile;
/**
*
* A facade for receiving notifcations for SMB file modifications through the
* JCIFS API.
*
* This class provides notification for file creation and deletion in a SMB/CIFS
* directory. A callback is produced on modification and the modified file name
* is given to the default handler.
*
* @author Ryan Beckett
* @version 1.0
*
*/
public class SmbNotification {
private NtlmPasswordAuthentication authentication;
private NotificationHandler handler;
private SmbFile smb;
private List<String> files;
private Logger logger;
private boolean running;
private boolean stopped;
/**
* Creates a new non-running notifier.
*
* @param url
* An smb directory path of the form
* <code>smb://host/dir/</code>. See {@link SmbFile} for more
* information on constructing URLs.
*
* @param authentication
* The authentication information.
*
* @param handler
* A default handler for notification callbacks.
*
* @throws IllegalArgumentException
* If <code>authentication</code> or <code>handler</code> are
* null.
*
* @throws MalformedURLException
* If <code>dir</code> is a malformed URL.
*
* @throws SmbException
* If an underlying communication error occurs.
*/
public SmbNotification(String url, NtlmPasswordAuthentication auth,
NotificationHandler handler) throws IllegalArgumentException,
MalformedURLException, SmbException {
super();
if (auth == null)
throw new IllegalArgumentException();
this.authentication = auth;
if (handler == null)
throw new IllegalArgumentException();
this.handler = handler;
logger = Logger.getLogger("SmbNotification");
files = new ArrayList<String>();
connect(url);
createFileList();
}
private void connect(String dir) throws MalformedURLException {
smb = new SmbFile(dir, authentication);
}
private void createFileList() throws SmbException {
for (String f : smb.list()) {
files.add(f);
}
}
/**
* Listen for modifications to the directory. A new thread is started that
* polls for modifications every <code>millis</code> milliseconds. If the
* notifier is running already, a new thread will not be created.
*
* @param millis
* The number of milliseconds to wait per poll.
*
* @return Returns <code>true</code> if the notifier is started and a new
* thread is spawned; otherwise, returns <code>false</code> if the
* notifier is running already.
*
* @throws IllegalStateException
* If <code>listen</code> is called after <code>close</code> is
* called.
*/
public boolean listen(final long millis) {
if (stopped)
throw new IllegalStateException("Cannot restart the notifier.");
if (running)
return false;
running = true;
Thread t = new Thread(new Runnable() {
public void run() {
while (running) {
try {
checkForFileDeletion();
checkForNewFile();
Thread.sleep(millis);
} catch (SmbException e) {
log(e.getMessage(), Level.SEVERE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
return true;
}
private void checkForFileDeletion() throws SmbException {
List<String> smbFiles = Arrays.asList(smb.list());
String f = null;
for (int i = 0; i < files.size(); i++)
f = files.get(i);
if (f != null && !smbFiles.contains(f)) {
files.remove(f);
handler.handleDeletedFile(f);
}
}
private void checkForNewFile() throws SmbException {
for (String f : smb.list()) {
if (!files.contains(f)) {
files.add(f);
handler.handleNewFile(f);
}
}
}
/**
* Stop listening for notifications. The polling thread is terminated. Once
* this method is called, if notifier is started again, an exception is
* thrown.
*/
public void stop() {
running = false;
stopped = true;
}
private void log(String message, Level level) {
logger.log(level, message);
}
/**
* Check whether the notifier is running.
*
* @return Returns <code>true</code> if the notifier is running; otherwise,
* returns <code>false</code>.
*/
public boolean isRunning() {
return running;
}
/**
* Get the callback handler.
*
* @return The handler.
*/
public NotificationHandler getHandler() {
return handler;
}
/**
* Set the callback handler
*
* @param handler
* The new handler.
*/
public void setHandler(NotificationHandler handler) {
this.handler = handler;
}
/**
* Get the authentication.
*
* @return The authentication.
*/
public NtlmPasswordAuthentication getAuthentication() {
return authentication;
}
/**
* Set the authentication. Setting a new authentication will have no
* effect. Once the notifier has made the connection, the user session
* will persist throughout the notifier's life cycle.
*
* @param handler
* The new authentication.
*/
public void setAuthentication(NtlmPasswordAuthentication authentication) {
this.authentication = authentication;
}
}
import static org.junit.Assert.assertEquals;
import java.net.MalformedURLException;
import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbException;
import org.junit.Before;
import org.junit.Test;
/**
*
* Unit tests for the {@link SmbNotification} API.
*
* @author Ryan Beckett
*/
public class SmbNotificationTest {
/* Note: If your running unit tests these strings need valid values. */
private final String url = "smb://host/dir/"; //directories must end with '/'
private final String domain = "Your WORKGROUP name";
private final String user = "Your username";
private final String pass = "Your password";
private NtlmPasswordAuthentication auth;
private NotificationHandler handler;
@Before
public final void setUp() {
String userInfo = domain+";"+user+":"+pass;
auth = new NtlmPasswordAuthentication(userInfo);
handler = new NotificationHandler() {
@Override
public void handleNewFile(String file) {
System.out.println(file + " added.");
}
@Override
public void handleDeletedFile(String file) {
System.out.println(file + " deleted.");
}
};
}
@Test(expected = IllegalArgumentException.class)
public final void testNullAuthentication() throws Exception {
new SmbNotification(url, null, handler);
}
@Test(expected = IllegalArgumentException.class)
public final void testNullHandler() throws Exception {
new SmbNotification(url, auth, null);
}
@Test(expected = MalformedURLException.class)
public final void testNullDirURL() throws Exception {
new SmbNotification(null, auth, handler);
}
@Test(expected = MalformedURLException.class)
public final void testMalformedDirURL() throws Exception {
new SmbNotification("smb//", auth, handler);
}
@Test(expected = SmbException.class)
public final void testInvalidAuthenticationCredentials() throws Exception {
String invalidUser = "invalidUser";
String invalidPass = "invalidPass";
String userInfo = domain+";"+invalidUser+":"+pass;
NtlmPasswordAuthentication invalidAuth = new NtlmPasswordAuthentication(userInfo);
new SmbNotification(url, invalidAuth, handler);
userInfo = domain+";"+user+":"+invalidPass;
invalidAuth = new NtlmPasswordAuthentication(userInfo);
new SmbNotification(url, invalidAuth, handler);
}
@Test(expected = IllegalStateException.class)
public final void testIllegalListenAfterClose() throws Exception {
SmbNotification notifier = new SmbNotification(url, auth, handler);
notifier.listen(500);
notifier.stop();
notifier.listen(500);
}
@Test
public final void testConsecutiveCallsToListen() throws Exception {
SmbNotification notifier = new SmbNotification(url, auth, handler);
assertEquals(new Boolean(true), new Boolean(notifier.listen(500)));
assertEquals(new Boolean(false), new Boolean(notifier.listen(500)));
notifier.stop();
}
@Test
public final void testNotificationTermination() throws Exception {
SmbNotification notifier = new SmbNotification(url, auth, handler);
assertEquals(new Boolean(false), new Boolean(notifier.isRunning()));
notifier.listen(500);
assertEquals(new Boolean(true), new Boolean(notifier.isRunning()));
notifier.stop();
assertEquals(new Boolean(false), new Boolean(notifier.isRunning()));
}
}
import jcifs.smb.NtlmPasswordAuthentication;
/**
*
* Runs the SMB directory notifier. Specify the SMB parameters as command-line
* arguments.
*
* @author Ryan Beckett
*
*/
public class SmbNotifier {
private SmbNotification notifier;
private NtlmPasswordAuthentication auth;
private NotificationHandler handler;
public static void main(String[] args) {
if (args.length < 4) {
System.out
.println("Usage: java SmbNotifier \"<smb url>\""
+ " \"<domain>\"" + " \"<login name>\""
+ " \"<password>\"");
System.exit(1);
}
new SmbNotifier().run(args[0], args[1], args[2], args[3]);
}
public void run(final String url, String domain, String user, String pass) {
String userInfo = domain + ";" + user + ":" + pass;
auth = new NtlmPasswordAuthentication(userInfo);
handler = new NotificationHandler() {
@Override
public void handleNewFile(String file) {
System.out.println(file + " added.");
}
@Override
public void handleDeletedFile(String file) {
System.out.println(file + " deleted.");
}
};
try {
notifier = new SmbNotification(url, auth, handler);
notifier.listen(500);
while (true)
;
// modify the SMB directory and watch the console!!
} catch (Exception e) {
e.printStackTrace();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment