com.sun.jna.platform.win32.W32FileMonitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jna-platform Show documentation
Show all versions of jna-platform Show documentation
Java Native Access Platform
/* Copyright (c) 2007 Timothy Wall, All Rights Reserved
*
* The contents of this file is dual-licensed under 2
* alternative Open Source/Free licenses: LGPL 2.1 or later and
* Apache License 2.0. (starting with JNA version 4.0.0).
*
* You can freely decide which license you want to apply to
* the project.
*
* You may obtain a copy of the LGPL License at:
*
* http://www.gnu.org/licenses/licenses.html
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "LGPL2.1".
*
* You may obtain a copy of the Apache License at:
*
* http://www.apache.org/licenses/
*
* A copy is also included in the downloadable source code package
* containing JNA, in file "AL2.0".
*/
package com.sun.jna.platform.win32;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.sun.jna.platform.FileMonitor;
import com.sun.jna.platform.win32.BaseTSD.ULONG_PTRByReference;
import com.sun.jna.platform.win32.WinBase.OVERLAPPED;
import com.sun.jna.platform.win32.WinNT.FILE_NOTIFY_INFORMATION;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import java.util.logging.Level;
import java.util.logging.Logger;
public class W32FileMonitor extends FileMonitor {
private static final Logger LOG = Logger.getLogger(W32FileMonitor.class.getName());
private static final int BUFFER_SIZE = 4096;
private class FileInfo {
public final File file;
public final HANDLE handle;
public final int notifyMask;
public final boolean recursive;
public final FILE_NOTIFY_INFORMATION info = new FILE_NOTIFY_INFORMATION(BUFFER_SIZE);
public final IntByReference infoLength = new IntByReference();
public final OVERLAPPED overlapped = new OVERLAPPED();
public FileInfo(File f, HANDLE h, int mask, boolean recurse) {
this.file = f;
this.handle = h;
this.notifyMask = mask;
this.recursive = recurse;
}
}
private Thread watcher;
private HANDLE port;
private final Map fileMap = new HashMap();
private final Map handleMap = new HashMap();
private boolean disposing = false;
private void handleChanges(FileInfo finfo) throws IOException {
Kernel32 klib = Kernel32.INSTANCE;
FILE_NOTIFY_INFORMATION fni = finfo.info;
// Need an explicit read, since data was filled in asynchronously
fni.read();
do {
FileEvent event = null;
File file = new File(finfo.file, fni.getFilename());
switch(fni.Action) {
case 0:
break;
case WinNT.FILE_ACTION_MODIFIED:
event = new FileEvent(file, FILE_MODIFIED);
break;
case WinNT.FILE_ACTION_ADDED:
event = new FileEvent(file, FILE_CREATED);
break;
case WinNT.FILE_ACTION_REMOVED:
event = new FileEvent(file, FILE_DELETED);
break;
case WinNT.FILE_ACTION_RENAMED_OLD_NAME:
event = new FileEvent(file, FILE_NAME_CHANGED_OLD);
break;
case WinNT.FILE_ACTION_RENAMED_NEW_NAME:
event = new FileEvent(file, FILE_NAME_CHANGED_NEW);
break;
default:
// TODO: other actions...
LOG.log(Level.WARNING, "Unrecognized file action ''{0}''", fni.Action);
}
if (event != null) {
notify(event);
}
fni = fni.next();
} while (fni != null);
// trigger the next read
if (!finfo.file.exists()) {
unwatch(finfo.file);
return;
}
if (!klib.ReadDirectoryChangesW(finfo.handle, finfo.info,
finfo.info.size(), finfo.recursive, finfo.notifyMask,
finfo.infoLength, finfo.overlapped, null)) {
if (!disposing) {
int err = klib.GetLastError();
throw new IOException("ReadDirectoryChangesW failed on "
+ finfo.file + ": '"
+ Kernel32Util.formatMessageFromLastErrorCode(err)
+ "' (" + err + ")");
}
}
}
private FileInfo waitForChange() {
IntByReference rcount = new IntByReference();
ULONG_PTRByReference rkey = new ULONG_PTRByReference();
PointerByReference roverlap = new PointerByReference();
if (! Kernel32.INSTANCE.GetQueuedCompletionStatus(port, rcount, rkey, roverlap, WinBase.INFINITE)) {
return null;
}
synchronized (this) {
return handleMap.get(new HANDLE(rkey.getValue().toPointer()));
}
}
private int convertMask(int mask) {
int result = 0;
if ((mask & FILE_CREATED) != 0) {
result |= WinNT.FILE_NOTIFY_CHANGE_CREATION;
}
if ((mask & FILE_DELETED) != 0) {
result |= WinNT.FILE_NOTIFY_CHANGE_NAME;
}
if ((mask & FILE_MODIFIED) != 0) {
result |= WinNT.FILE_NOTIFY_CHANGE_LAST_WRITE;
}
if ((mask & FILE_RENAMED) != 0) {
result |= WinNT.FILE_NOTIFY_CHANGE_NAME;
}
if ((mask & FILE_SIZE_CHANGED) != 0) {
result |= WinNT.FILE_NOTIFY_CHANGE_SIZE;
}
if ((mask & FILE_ACCESSED) != 0) {
result |= WinNT.FILE_NOTIFY_CHANGE_LAST_ACCESS;
}
if ((mask & FILE_ATTRIBUTES_CHANGED) != 0) {
result |= WinNT.FILE_NOTIFY_CHANGE_ATTRIBUTES;
}
if ((mask & FILE_SECURITY_CHANGED) != 0) {
result |= WinNT.FILE_NOTIFY_CHANGE_SECURITY;
}
return result;
}
private static int watcherThreadID;
@Override
protected synchronized void watch(File file, int eventMask, boolean recursive) throws IOException {
File dir = file;
if (!dir.isDirectory()) {
recursive = false;
dir = file.getParentFile();
}
while (dir != null && !dir.exists()) {
recursive = true;
dir = dir.getParentFile();
}
if (dir == null) {
throw new FileNotFoundException("No ancestor found for " + file);
}
Kernel32 klib = Kernel32.INSTANCE;
int mask = WinNT.FILE_SHARE_READ
| WinNT.FILE_SHARE_WRITE | WinNT.FILE_SHARE_DELETE;
int flags = WinNT.FILE_FLAG_BACKUP_SEMANTICS
| WinNT.FILE_FLAG_OVERLAPPED;
HANDLE handle = klib.CreateFile(file.getAbsolutePath(),
WinNT.FILE_LIST_DIRECTORY,
mask, null, WinNT.OPEN_EXISTING,
flags, null);
if (WinBase.INVALID_HANDLE_VALUE.equals(handle)) {
throw new IOException("Unable to open " + file + " ("
+ klib.GetLastError() + ")");
}
int notifyMask = convertMask(eventMask);
FileInfo finfo = new FileInfo(file, handle, notifyMask, recursive);
fileMap.put(file, finfo);
handleMap.put(handle, finfo);
// Existing port is returned
port = klib.CreateIoCompletionPort(handle, port, handle.getPointer(), 0);
if (WinBase.INVALID_HANDLE_VALUE.equals(port)) {
throw new IOException("Unable to create/use I/O Completion port "
+ "for " + file + " ("
+ klib.GetLastError() + ")");
}
// TODO: use FileIOCompletionRoutine callback method instead of a
// dedicated thread
if (!klib.ReadDirectoryChangesW(handle, finfo.info, finfo.info.size(),
recursive, notifyMask, finfo.infoLength,
finfo.overlapped, null)) {
int err = klib.GetLastError();
throw new IOException("ReadDirectoryChangesW failed on "
+ finfo.file + ", handle " + handle
+ ": '" + Kernel32Util.formatMessageFromLastErrorCode(err)
+ "' (" + err + ")");
}
if (watcher == null) {
watcher = new Thread("W32 File Monitor-" + (watcherThreadID++)) {
@Override
public void run() {
FileInfo finfo;
while (true) {
finfo = waitForChange();
if (finfo == null) {
synchronized (W32FileMonitor.this) {
if (fileMap.isEmpty()) {
watcher = null;
break;
}
}
continue;
}
try {
handleChanges(finfo);
} catch (IOException e) {
// TODO: how is this best handled?
e.printStackTrace();
}
}
}
};
watcher.setDaemon(true);
watcher.start();
}
}
@Override
protected synchronized void unwatch(File file) {
FileInfo finfo = fileMap.remove(file);
if (finfo != null) {
handleMap.remove(finfo.handle);
Kernel32 klib = Kernel32.INSTANCE;
// bug: the watcher may still be processing this file
klib.CloseHandle(finfo.handle); // TODO check error code if failed to close
}
}
@Override
public synchronized void dispose() {
disposing = true;
// unwatch any remaining files in map, allows watcher thread to exit
int i = 0;
for (Object[] keys = fileMap.keySet().toArray(); !fileMap.isEmpty();) {
unwatch((File)keys[i++]);
}
Kernel32 klib = Kernel32.INSTANCE;
klib.PostQueuedCompletionStatus(port, 0, null, null);
klib.CloseHandle(port); // TODO check error code if failed to close
port = null;
watcher = null;
}
}