/* Copyright (C) 2005-2011 Fabio Riccardi */
package com.lightcrafts.utils.directory;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.lightcrafts.utils.file.FileUtil;
import com.lightcrafts.platform.windows.WindowsFileUtil;
import sun.awt.shell.ShellFolder;
/**
* A <code>WindowsDirectoryMonitor</code> is-a {@link DirectoryMonitor} for
* monitoring directories of a Windows system.
*
* @author Paul J. Lucas [paul@lightcrafts.com]
*/
public final class WindowsDirectoryMonitor extends DirectoryMonitor {
////////// public /////////////////////////////////////////////////////////
/**
* Construct a <code>WindowsDirectoryMonitor</code>.
*/
public WindowsDirectoryMonitor() {
start();
}
/**
* Add a directory to be monitored. Adding the same directory more than
* once is guaranteed to be harmless.
*
* @param directory The directory to be monitored.
*/
public void addDirectory( File directory ) {
synchronized ( m_dirMap ) {
if ( !m_dirMap.containsKey( directory ) ) {
try {
final int value;
if ( WindowsFileUtil.isGUID( directory ) )
value = newHashCode( directory );
else
value = newHandle( directory.getAbsolutePath() );
m_dirMap.put( directory, value );
//noinspection ConstantConditions
if ( DEBUG )
System.out.println(
"WindowsDirectoryMonitor: added " + directory
);
}
catch ( IOException e ) {
// ignore
}
}
}
}
/**
* {@inheritDoc}
*/
public void dispose() {
super.dispose();
synchronized ( m_dirMap ) {
for ( Map.Entry<File,Integer> entry : m_dirMap.entrySet() )
if ( !WindowsFileUtil.isGUID( entry.getKey() ) )
try {
disposeHandle( entry.getValue() );
}
catch ( IOException e ) {
// ignore
}
}
}
/**
* Remove a directory from being monitored.
*
* @param directory The directory to remove.
* @return Returns <code>true</code> only if the directory was being
* monitored and thus removed.
*/
public boolean removeDirectory( File directory ) {
final Integer value;
synchronized ( m_dirMap ) {
value = m_dirMap.remove( directory );
}
if ( value != null ) {
if ( !WindowsFileUtil.isGUID( directory ) ) {
try {
disposeHandle( value );
}
catch ( IOException e ) {
// ignore
}
}
//noinspection ConstantConditions
if ( DEBUG )
System.out.println(
"WindowsDirectoryMonitor: removed " + directory
);
return true;
}
return false;
}
////////// protected //////////////////////////////////////////////////////
/**
* {@inheritDoc}
*/
protected File[] getMonitoredDirectories() {
synchronized ( m_dirMap ) {
return m_dirMap.keySet().toArray( new File[0] );
}
}
/**
* {@inheritDoc}
*/
protected boolean hasChanged( File dir ) {
if ( dir.exists() ) {
//
// The directory still exists: check to see whether its changed
// since the last time we checked.
//
synchronized ( m_dirMap ) {
final Integer value = m_dirMap.get( dir );
if ( value != null )
if ( WindowsFileUtil.isGUID( dir ) ) {
final int newHashCode = newHashCode( dir );
if ( newHashCode != value ) {
m_dirMap.put( dir, newHashCode );
return true;
}
} else
try {
if ( hasChanged( value ) ) {
//
// The contents of the directory have changed.
//
return true;
}
}
catch ( IOException e ) {
// ignore
}
}
} else {
//
// The directory has disappeared from the filesystem: remove it
// from the collection of directories we're monitoring.
//
return removeDirectory( dir );
}
return false;
}
////////// private ////////////////////////////////////////////////////////
/**
* Dispose of the given native Windows change notification object referred
* to by the given handle.
*
* @param handle The handle to the native Windows change notification
* object to dispose of.
*/
private static native void disposeHandle( int handle ) throws IOException;
/**
* Checks whether the directory referred to by the given native Windows
* change notification <code>HANDLE</code> has changed.
*
* @param handle The handle to the native Windows change notification
* object of a directory being monitored.
* @return Returns <code>true</code> only if the directory has changed.
*/
private static native boolean hasChanged( int handle ) throws IOException;
/**
* Creates a new native Windows <code>HANDLE</code> that refers to a
* Windows change notification object that is used to monitor the given
* directory.
*
* @param dir The full path of the directory to monitor.
* @return Returns a native Windows <code>HANDLE</code> object (casted to
* an <code>int</code>) that refers to a Windows change notification object
* that is used to monitor the directory. The value should only be passed
* to native methods and should be considered opaque and not touched from
* Java.
*/
private static native int newHandle( String dir ) throws IOException;
/**
* Computes a hash code for the given directory.
*
* @param dir The directory to compute the hash code for.
* @return Returns said hash code.
*/
private static int newHashCode( File dir ) {
if ( dir instanceof ShellFolder ) {
//
// This is a hack to work around the problem of scanning the
// "Network" pseudo-folder that takes a long time.
//
final ShellFolder sf = (ShellFolder)dir;
if ( sf.getDisplayName().equals( "Network" ) )
return 0;
}
final File[] contents =
FileUtil.listFiles( dir, DirectoryOnlyFilter.INSTANCE, false );
int hashCode = 0;
if ( contents != null )
for ( File file : contents )
hashCode ^= file.hashCode();
return hashCode;
}
/**
* The collection of directories to monitor. The key is the full path to a
* directory and the value is a native Windows <code>HANDLE</code> object
* (casted to an <code>int</code>) that refers to a Windows change
* notification object that is used to monitor the directory.
* <p>
* The value should only be gotten from and passed to native methods and
* should be considered opaque and not touched from Java.
*/
private final Map<File,Integer> m_dirMap = new HashMap<File,Integer>();
static {
System.loadLibrary( "Windows" );
}
////////// main() /////////////////////////////////////////////////////////
private static final class TestListener implements DirectoryListener {
public void directoryChanged( File dir ) {
System.out.println( dir.getAbsolutePath() );
}
}
public static void main( String[] args ) throws Exception {
final DirectoryMonitor monitor = new WindowsDirectoryMonitor();
final DirectoryListener listener = new TestListener();
monitor.addListener( listener );
final BufferedReader commandLine =
new BufferedReader( new InputStreamReader( System.in ) );
//noinspection InfiniteLoopStatement
while ( true ) {
System.out.print( "> " );
final String cmd = commandLine.readLine();
if ( cmd.length() == 0 )
continue;
if ( cmd.startsWith( "+ " ) ) {
final String dir = cmd.substring( 2 );
monitor.addDirectory( new File( dir ) );
continue;
}
if ( cmd.startsWith( "- " ) ) {
final String dir = cmd.substring( 2 );
monitor.removeDirectory( new File( dir ) );
continue;
}
System.err.println( "Unknown command" );
}
}
}
/* vim:set et sw=4 ts=4: */