I have a component that should do something every time a file in the watched directory is created,updated or deleted.
For the sake of simplicity, lets say it prints the file names to Sysout.
#Component
public class MyComponent {
#Autowired
MyComponentWatcher myComponentWatcher
#PostConstruct
public void init(){
myComponentWatcher.registerWatchService();
}
}
#Component
public class MyComponentWatcher {
#Value("${somePath}")
private String myPath;
private File myDir;
#PostConstruct
public void init() {
this.myDir = new File(myPath);
}
#Async
public void registerWatchService() throws IOException, InterruptedException {
WatchService watchService
= FileSystems.getDefault().newWatchService();
Path path = myDir.toPath();
path.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println(
"Event kind:" + event.kind()
+ ". File affected: " + event.context() + ".");
}
key.reset();
File[] files = myDir.listFiles();
System.out.println(files); //!!!
}
}
}
GIVEN my dir contains 1 file
WHEN I create another file
THEN System.out.println(files); returns the old and the new file
-> I only get the old file!
I wrote a test case that uses Files.write(filePath, fileContent.getBytes(), CREATE_NEW); to create the test file and
#AfterEach
void tearDown() {
File toDelete = new File(filePath.toString());
toDelete.delete();
}
to delete the test file after the test.
In total the watcher triggers 3 times with the following lists of events:
[ENTRY_CREATE, ENTRY_MODIFY]
[ENTRY_MODIFY]
[ENTRY_MODIFY]
[ENTRY_DELETE]
Your code is working fine as expected and written.
to achieve your output, you need to recreate the object for File class.
// we are creating the object just after the constructor
#PostConstruct
public void init() {
this.myDir = new File(myPath);
}
but then you modified your path but not the actual instance variable:
please change your code File[] files = new File(myPath).listFiles();
Related
I can monitor a directory by registering cwith a WatchKey (there are tons of examples on the web) however this watcher catches every single event. E.g. On windows If am monitoring the d:/temp dir and I create a new .txt file and rename it I get the following events.
ENTRY_CREATE: d:\temp\test\New Text Document.txt
ENTRY_MODIFY: d:\temp\test
ENTRY_DELETE: d:\temp\test\New Text Document.txt
ENTRY_CREATE: d:\temp\test\test.txt
ENTRY_MODIFY: d:\temp\test
I want to perform an action when the new file is created or updated. However I don't want the action to run 5 times in the above example.
My 1st Idea: As I only need to run the action (in this case a push to a private Git server) once every now an then (e.g. check every 10 seconds only if there are changes to the monitored directory and only then perform the push) I thought of having an object with a boolean parameter that I can get and set from within separate threads.
Now this works kinda ok (unless the gurus can help educated me as to why this is a terrible idea) The problem is that if a file event is caught during the SendToGit thread's operation and this operation completes it sets the "Found" parameter to false. Immediately thereafter one of the other events are caught (as in the example above) they will set the "Found" parameter to true again. This is not ideal as I will then run the SendToGit operation immediately again which will be unnecessary.
My 2nd Idea Investigate pausing the check for changes in the MonitorFolder thread until the SendToGit operation is complete (I.e. Keep checking if the ChangesFound Found parameter has been set back to false. When this parameter is false start checking for changes again.
Questions
Is this an acceptable way to go or have I gone down a rabbit hole with no hope of return?
If I go down the road of my 2nd idea what happens if I am busy with the SendToGit operation and a change is made in the monitoring folder? I suspect that this will not be identified and I may miss changes.
The Rest of the code
ChangesFound.java
package com.acme;
public class ChangesFound {
private boolean found = false;
public boolean wereFound() {
return this.found;
}
public void setFound(boolean commitToGit) {
this.found = commitToGit;
}
}
In my main app I start 2 threads.
MonitorFolder.java Monitors the directory and when Watcher events are found set the ChangesFound variable "found" to true.
SendToGit.java Every 10 seconds checks if the ChangesFound variable found is true and if it is performs the push. (or in this case just prints a message)
Here is my App that starts the threads:
package com.acme;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
public class App {
private static ChangesFound chg;
public static void main(String[] args) throws IOException {
String dirToMonitor = "D:/Temp";
boolean recursive = true;
chg = new ChangesFound();
Runnable r = new SendToGit(chg);
new Thread(r).start();
Path dir = Paths.get(dirToMonitor);
Runnable m = new MonitorFolder(chg, dir, recursive);
new Thread(m).start();
}
}
SendToGit.java
package com.acme;
public class SendToGit implements Runnable {
private ChangesFound changes;
public SendToGit(ChangesFound chg) {
changes = chg;
}
public void run() {
while (true) {
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
System.out.println(java.time.LocalDateTime.now() + " [SendToGit] waking up.");
if (changes.wereFound()) {
System.out.println("\t***** CHANGES FOUND push to Git.");
changes.setFound(false);
} else {
System.out.println("\t***** Nothing changed.");
}
}
}
}
MonitorFolder.java (Apologies for the long class I only added this here in case it helps someone else.)
package com.acme;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
public class MonitorFolder implements Runnable {
private static WatchService watcher;
private static Map<WatchKey, Path> keys;
private static boolean recursive;
private static boolean trace = false;
private static boolean commitGit = false;
private static ChangesFound changes;
#SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>) event;
}
/**
* Creates a WatchService and registers the given directory
*/
MonitorFolder(ChangesFound chg, Path dir, boolean rec) throws IOException {
changes = chg;
watcher = FileSystems.getDefault().newWatchService();
keys = new HashMap<WatchKey, Path>();
recursive = rec;
if (recursive) {
System.out.format("[MonitorFolder] Scanning %s ...\n", dir);
registerAll(dir);
System.out.println("Done.");
} else {
register(dir);
}
// enable trace after initial registration
this.trace = true;
}
/**
* Register the given directory with the WatchService
*/
private static void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev, dir);
}
}
}
keys.put(key, dir);
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private static void registerAll(final Path start) throws IOException {
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
register(dir);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Process all events for keys queued to the watcher
*/
public void run() {
for (;;) {
// wait for key to be signalled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
System.err.println("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
System.out.println("Something about Overflow");
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> ev = cast(event);
Path name = ev.context();
Path child = dir.resolve(name);
// print out event and set ChangesFound object Found parameter to True
System.out.format("[MonitorFolder] " + java.time.LocalDateTime.now() + " - %s: %s\n", event.kind().name(), child);
changes.setFound(true);
// 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()) {
System.out.println("keys.isEmpty");
break;
}
}
}
}
}
Both of your strategies will lead to issues because the Watch Service is very verbose and sends many messages when maybe one or two is actually needed to your downstream handling - so sometimes you may do unnecessary work or miss events.
When using WatchService you could collate multiple notifications together and pass on as ONE event listing a sets of recent deletes, creates and updates:
DELETE followed by CREATE => sent as UPDATE
CREATE followed by MODIFY => sent as CREATE
CREATE or MODIFY followed by DELETE => sent as DELETE
Instead of calling WatchService.take() and acting on each message, use WatchService.poll(timeout) and only when nothing is returned act on the union of preceeding set of events as one - not individually after each successful poll.
It is easier to decouple the problems as two components so that you don't repeat the WatchService code the next time you need it:
A watch manager which handles watch service + dir registrations and collates the duplicates to send to event listeners as ONE group
A Listener class to receive the group of changes and act on the set.
This example may help illustrate - see WatchExample which is the manager which sets up the registrations BUT passes on much fewer events to the callback defined by setListener. You could set up MonitorFolder like WatchExample to reduce the events discovered, and make your code in SendToGit as a Listener which is called on demand with the aggregated set of fileChange(deletes, creates, updates).
public static void main(String[] args) throws IOException, InterruptedException {
final List<Path> dirs = Arrays.stream(args).map(Path::of).map(Path::toAbsolutePath).collect(Collectors.toList());
Kind<?> [] kinds = { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE};
// Should launch WatchExample PER Filesystem:
WatchExample w = new WatchExample();
w.setListener(WatchExample::fireEvents);
for(Path dir : dirs)
w.register(kinds, dir);
// For 2 or more WatchExample use: new Thread(w[n]::run).start();
w.run();
}
public class WatchExample implements Runnable {
private final Set<Path> created = new LinkedHashSet<>();
private final Set<Path> updated = new LinkedHashSet<>();
private final Set<Path> deleted = new LinkedHashSet<>();
private volatile boolean appIsRunning = true;
// Decide how sensitive the polling is:
private final int pollmillis = 100;
private WatchService ws;
private Listener listener = WatchExample::fireEvents;
#FunctionalInterface
interface Listener
{
public void fileChange(Set<Path> deleted, Set<Path> created, Set<Path> modified);
}
WatchExample() {
}
public void setListener(Listener listener) {
this.listener = listener;
}
public void shutdown() {
System.out.println("shutdown()");
this.appIsRunning = false;
}
public void run() {
System.out.println();
System.out.println("run() START watch");
System.out.println();
try(WatchService autoclose = ws) {
while(appIsRunning) {
boolean hasPending = created.size() + updated.size() + deleted.size() > 0;
System.out.println((hasPending ? "ws.poll("+pollmillis+")" : "ws.take()")+" as hasPending="+hasPending);
// Use poll if last cycle has some events, as take() may block
WatchKey wk = hasPending ? ws.poll(pollmillis,TimeUnit.MILLISECONDS) : ws.take();
if (wk != null) {
for (WatchEvent<?> event : wk.pollEvents()) {
Path parent = (Path) wk.watchable();
Path eventPath = (Path) event.context();
storeEvent(event.kind(), parent.resolve(eventPath));
}
boolean valid = wk.reset();
if (!valid) {
System.out.println("Check the path, dir may be deleted "+wk);
}
}
System.out.println("PENDING: cre="+created.size()+" mod="+updated.size()+" del="+deleted.size());
// This only sends new notifications when there was NO event this cycle:
if (wk == null && hasPending) {
listener.fileChange(deleted, created, updated);
deleted.clear();
created.clear();
updated.clear();
}
}
}
catch (InterruptedException e) {
System.out.println("Watch was interrupted, sending final updates");
fireEvents(deleted, created, updated);
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
System.out.println("run() END watch");
}
public void register(Kind<?> [] kinds, Path dir) throws IOException {
System.out.println("register watch for "+dir);
// If dirs are from different filesystems WatchService will give errors later
if (this.ws == null) {
ws = dir.getFileSystem().newWatchService();
}
dir.register(ws, kinds);
}
/**
* Save event for later processing by event kind EXCEPT for:
* <li>DELETE followed by CREATE => store as MODIFY
* <li>CREATE followed by MODIFY => store as CREATE
* <li>CREATE or MODIFY followed by DELETE => store as DELETE
*/
private void
storeEvent(Kind<?> kind, Path path) {
System.out.println("STORE "+kind+" path:"+path);
boolean cre = false;
boolean mod = false;
boolean del = kind == StandardWatchEventKinds.ENTRY_DELETE;
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
mod = deleted.contains(path);
cre = !mod;
}
else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
cre = created.contains(path);
mod = !cre;
}
addOrRemove(created, cre, path);
addOrRemove(updated, mod, path);
addOrRemove(deleted, del, path);
}
// Add or remove from the set:
private static void addOrRemove(Set<Path> set, boolean add, Path path) {
if (add) set.add(path);
else set.remove(path);
}
public static void fireEvents(Set<Path> deleted, Set<Path> created, Set<Path> modified) {
System.out.println();
System.out.println("fireEvents START");
for (Path path : deleted)
System.out.println(" DELETED: "+path);
for (Path path : created)
System.out.println(" CREATED: "+path);
for (Path path : modified)
System.out.println(" UPDATED: "+path);
System.out.println("fireEvents END");
System.out.println();
}
}
Hi I would like to process files inside many sub directories using Java. Psuedo code would be
while(mainDir.hasMoreDirectory())
{
getFilesFromCurrentDirectory()
passThoseFilesAsArgumentToProcess()
}
I am currently using the following code
public void list(File file) {
System.out.println(file.getName());
File[] children = file.listFiles();
for (File child : children) {
list(child);
}
}
Above code just lists files. Other thing I can do is I have to store list of files and directories in a list and then process in another loop. But I am not able to come up with what I want as show in pseudo code. I am new to Files Directories please help. Thanks in advance.
If you are using Java 7, you can harness the enhanced functionality of NIO in the form of the Files.walkFileTree method. Traversing the file system has never been easier in Java.
There is a short tutorial on it's usage here.
It implements the visitor pattern so you don't need to worry about the traversal algorithm itself, only specify what you want to do with each entry.
When traveling a directory tree in Java 7 use the Paths and Files functionality. They not only ease reading of directories and files, they're way faster then the "old" File way.
Assume you have two directories: mainDirand otherDirand you want to walk thru all directories of mainDir down to its leaves. With each entry in maiondir (file, sub-directory, symbolic link, ...) you want to compare this entry and its attributes (size, modification time, ...) against the entry at the same position in the otherDir.
Then this would be your code:
public final void test() throws IOException, InterruptedException {
final Path mainDir = Paths.get("absolute path to your main directory to read from");
final Path otherDir = Paths.get("absolute path to your other directory to compare");
// Walk thru mainDir directory
Files.walkFileTree(mainDir, new FileVisitor<Path>() {
#Override
public FileVisitResult preVisitDirectory(Path path,
BasicFileAttributes atts) throws IOException {
return visitFile(path, atts);
}
#Override
public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
throws IOException {
// I've seen two implementations on windows and MacOSX. One has passed the relative path, one the absolute path.
// This works in both cases
Path relativePath = mainDir.relativize(mainDir.resolve(path));
BasicFileAttributes otherAtts = Files.readAttributes(otherDir.resolve(relativePath), BasicFileAttributes.class);
// Do your comparison logic here:
compareEntries(mainDir, otherDir, relativePath, mainAtts, otherAtts);
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult postVisitDirectory(Path path,
IOException exc) throws IOException {
// TODO Auto-generated method stub
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult visitFileFailed(Path path, IOException exc)
throws IOException {
exc.printStackTrace();
// If the root directory has failed it makes no sense to continue
return (path.equals(mainDir))? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
}
});
}
What it not does:
Find entries that do exist in otherDirbut not in maindir
Path and BasicFileAttributes are not Serializable, so there's no easy way to do this walk on two different machines.
A method like this would return you a List of all the files recursively within a directory. You can either operate on the returned List or replace the rtn.add calls with your processing.
Beware that this method doesn't have anything to stop it getting stuck in circular symlinks.
public static List<File> getFilesRecursive(File s)
{
ArrayList<File> rtn = new ArrayList<File>();
File[] contents = s.listFiles();
for(int i = 0; i<contents.length; i++)
{
if(contents[i].isDirectory()){
rtn.addAll(getFilesRecursive(contents[i]));
}else{
rtn.add(contents[i]);
}
}
return rtn;
}
Maybe this piece of code helps you:
public void traverse(String path) {
File root = new File(path);
File[] list = root.listFiles();
if (list == null) return;
for (File file : list) {
if (file.isDirectory()) {
traverse(file.getAbsolutePath());
System.out.println("Directory: " + file.getAbsoluteFile());
} else {
System.out.println("File: " + file.getAbsoluteFile());
}
}
}
Will following do?
public void list(File file) {
File[] children = file.listFiles();
if (children != null) {
process(children);
for (File child : children) {
if (child.isDirectory()) {
list(child);
}
}
} else {
process(new File[]{file});
}
}
private void process(File[] children) {
for (File child : children) {
if (child.isFile()) {
// process normal file
}
}
}
private static List<File> allFiles = new ArrayList<File>();
private static void processFiles(String rootDirectory) {
File rootDir = new File(rootDirectory);
if (rootDir.exists()) {
traverseDirectories(rootDir);
}
}
private static void traverseDirectories(File file) {
// add all files and directories to list.
allFiles.add(file);
if (file.isDirectory()) {
File[] fileList = file.listFiles();
for (File fileHandle : fileList) {
traverseDirectories(fileHandle);
}
} else {
// call to process file
System.out.println("Call to process file " + file.getAbsolutePath());
}
}
I have make some code for detect changed in directory C:/java/newfolder it working good. i have given below.
import java.nio.file.*;
import java.util.List;
public class DirectoryWatchExample {
public static void testForDirectoryChange(Path myDir){
try {
WatchService watcher = myDir.getFileSystem().newWatchService();
myDir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
WatchKey watckKey = watcher.take();
List<WatchEvent<?>> events = watckKey.pollEvents();
for (WatchEvent event : events) {
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
System.out.println("Created: " + event.context().toString());
}
if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
System.out.println("Delete: " + event.context().toString());
}
if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
System.out.println("Modify: " + event.context().toString());
}
}
} catch (Exception e) {
System.out.println("Error: " + e.toString());
}
}
public static void main (String[] args) {
Path myDir = Paths.get("c:/java/newfolder/");
//define a folder root
testForDirectoryChange(myDir);
}
}
now i watching only the directory. But i need to watch only the all sub directory.
for ex : c:/java/newfolder/folder1, c:/java/newfolder/folder2, c:/java/newfolder/folder3......etc
i given examples above sub directory
c:/java/newfolder/*..
i need to watch the all sub directory give me some solutions ?
What you want to do is register the WatchService recursively and continue to register it upon subsequent ENTRY_CREATE events. A complete example is provided by Oracle here: http://docs.oracle.com/javase/tutorial/essential/io/examples/WatchDir.java
Normally I wouldn't place a post with a single link, however I doubt Oracle will be taking down a fairly basic tutorial and the answer is far too verbose. Just in case though, examples are easily found by searching for "java watchservice recursive".
Here is what I came with..
final WatchService watcher = FileSystems.getDefault().newWatchService();
final SimpleFileVisitor<Path> fileVisitor = new SimpleFileVisitor<Path>(){
#Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException
{
dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
return FileVisitResult.CONTINUE;
}
};
Files.walkFileTree(Paths.get("/directory/to/monitor"), fileVisitor);
I'm not familiar with the Path API, so take the following with a grain of salt.
You're registering one directory for monitoring, and will receive notifications whenever one of its direct descendants is modified / created / deleted.
The first thing you need to do is register all of its subdirectories for watching:
// Used to filter out non-directory files.
// This might need to filter '.' and '..' out, not sure whether they're returned.
public class DirectoryFilter implements FileFilter {
public boolean accept(File file) {
return file.isDirectory();
}
}
// Add this at the end of your testForDirectoryChange method
for(File dir: myDir.toFile().listFiles(new DirectoryFilter())) {
testForDirectoryChange(dir.toPath());
}
This will recursively explore your file structure and register every directory for watching.
Note that if your directory tree is too deep, recursion might not be an acceptable solution and you might need to 'iterify' it.
The second thing you need to do is, whenever you receive a directory creation event, not forget to register the new directory for monitoring.
That's how I'd do it anyway, but not having a valid Java 1.7 installation at hand, I can't test it. Do let me know if it works!
So what you want is listing the files/subdirectories within a directory?
You can use:
File dir = new File("c:/java/newfolder/");
File[] files = dir.listFiles();
listFiles() gives you the Files in a directory or null it it's not a directory.
Then you can do:
for (File file: files){
if (file.listFiles != null){
//It's a subdirectory
File [] subdirFiles = file.listfiles;
}
}
When I'm trying to register a file instead of a directory java.nio.file.NotDirectoryException is thrown. Can I listen for a single file change, not the whole directory?
Just filter the events for the file you want in the directory:
final Path path = FileSystems.getDefault().getPath(System.getProperty("user.home"), "Desktop");
System.out.println(path);
try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
final WatchKey watchKey = path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
final WatchKey wk = watchService.take();
for (WatchEvent<?> event : wk.pollEvents()) {
//we only register "ENTRY_MODIFY" so the context is always a Path.
final Path changed = (Path) event.context();
System.out.println(changed);
if (changed.endsWith("myFile.txt")) {
System.out.println("My file has changed");
}
}
// reset the key
boolean valid = wk.reset();
if (!valid) {
System.out.println("Key has been unregisterede");
}
}
}
Here we check whether the changed file is "myFile.txt", if it is then do whatever.
Other answers are right that you must watch a directory and filter for your particular file. However, you probably want a thread running in the background. The accepted answer can block indefinitely on watchService.take(); and doesn't close the WatchService. A solution suitable for a separate thread might look like:
public class FileWatcher extends Thread {
private final File file;
private AtomicBoolean stop = new AtomicBoolean(false);
public FileWatcher(File file) {
this.file = file;
}
public boolean isStopped() { return stop.get(); }
public void stopThread() { stop.set(true); }
public void doOnChange() {
// Do whatever action you want here
}
#Override
public void run() {
try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
Path path = file.toPath().getParent();
path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
while (!isStopped()) {
WatchKey key;
try { key = watcher.poll(25, TimeUnit.MILLISECONDS); }
catch (InterruptedException e) { return; }
if (key == null) { Thread.yield(); continue; }
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
#SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
if (kind == StandardWatchEventKinds.OVERFLOW) {
Thread.yield();
continue;
} else if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY
&& filename.toString().equals(file.getName())) {
doOnChange();
}
boolean valid = key.reset();
if (!valid) { break; }
}
Thread.yield();
}
} catch (Throwable e) {
// Log or rethrow the error
}
}
}
I tried working from the accepted answer and this article. You should be able to use this thread with new FileWatcher(new File("/home/me/myfile")).start() and stop it by calling stopThread() on the thread.
No it isn't possible to register a file, the watch service doesn't work this way. But registering a directory actually watches changes on the directory children (the files and sub-directories), not the changes on the directory itself.
If you want to watch a file, then you register the containing directory with the watch service. Path.register() documentation says:
WatchKey java.nio.file.Path.register(WatchService watcher, Kind[] events, Modifier...
modifiers) throws IOException
Registers the file located by this path with a watch service.
In this release, this path locates a directory that exists. The directory is registered with the watch service so that entries in the directory can be watched
Then you need to process events on entries, and detect those related to the file you are interested in, by checking the context value of the event. The context value represents the name of the entry (actually the path of the entry relatively to the path of its parent, which is exactly the child name). You have an example here.
Apache offers a FileWatchDog class with a doOnChange method.
private class SomeWatchFile extends FileWatchdog {
protected SomeWatchFile(String filename) {
super(filename);
}
#Override
protected void doOnChange() {
fileChanged= true;
}
}
And where ever you want you can start this thread:
SomeWatchFile someWatchFile = new SomeWatchFile (path);
someWatchFile.start();
The FileWatchDog class polls a file's lastModified() timestamp. The native WatchService from Java NIO is more efficient, since notifications are immediate.
You cannot watch an individual file directly but you can filter out what you don't need.
Here is my FileWatcher class implementation:
import java.io.File;
import java.nio.file.*;
import java.nio.file.WatchEvent.Kind;
import static java.nio.file.StandardWatchEventKinds.*;
public abstract class FileWatcher
{
private Path folderPath;
private String watchFile;
public FileWatcher(String watchFile)
{
Path filePath = Paths.get(watchFile);
boolean isRegularFile = Files.isRegularFile(filePath);
if (!isRegularFile)
{
// Do not allow this to be a folder since we want to watch files
throw new IllegalArgumentException(watchFile + " is not a regular file");
}
// This is always a folder
folderPath = filePath.getParent();
// Keep this relative to the watched folder
this.watchFile = watchFile.replace(folderPath.toString() + File.separator, "");
}
public void watchFile() throws Exception
{
// We obtain the file system of the Path
FileSystem fileSystem = folderPath.getFileSystem();
// We create the new WatchService using the try-with-resources block
try (WatchService service = fileSystem.newWatchService())
{
// We watch for modification events
folderPath.register(service, ENTRY_MODIFY);
// Start the infinite polling loop
while (true)
{
// Wait for the next event
WatchKey watchKey = service.take();
for (WatchEvent<?> watchEvent : watchKey.pollEvents())
{
// Get the type of the event
Kind<?> kind = watchEvent.kind();
if (kind == ENTRY_MODIFY)
{
Path watchEventPath = (Path) watchEvent.context();
// Call this if the right file is involved
if (watchEventPath.toString().equals(watchFile))
{
onModified();
}
}
}
if (!watchKey.reset())
{
// Exit if no longer valid
break;
}
}
}
}
public abstract void onModified();
}
To use this, you just have to extend and implement the onModified() method like so:
import java.io.File;
public class MyFileWatcher extends FileWatcher
{
public MyFileWatcher(String watchFile)
{
super(watchFile);
}
#Override
public void onModified()
{
System.out.println("Modified!");
}
}
Finally, start watching the file:
String watchFile = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Test.txt";
FileWatcher fileWatcher = new MyFileWatcher(watchFile);
fileWatcher.watchFile();
Not sure about others, but I groan at the amount of code needed to watch a single file for changes using the basic WatchService API. It has to be simpler!
Here are a couple of alternatives using third party libraries:
Using Apache Commons Configuration
Using spring-loaded package from the Spring Framework (didn't find an example implementation for this off-hand, but it looks straight-forward to use)
I have created a wrapper around Java 1.7's WatchService that allows registering a directory and any number of glob patterns. This class will take care of the filtering and only emit events you are interested in.
try {
DirectoryWatchService watchService = new SimpleDirectoryWatchService(); // May throw
watchService.register( // May throw
new DirectoryWatchService.OnFileChangeListener() {
#Override
public void onFileCreate(String filePath) {
// File created
}
#Override
public void onFileModify(String filePath) {
// File modified
}
#Override
public void onFileDelete(String filePath) {
// File deleted
}
},
<directory>, // Directory to watch
<file-glob-pattern-1>, // E.g. "*.log"
<file-glob-pattern-2>, // E.g. "input-?.txt"
<file-glob-pattern-3>, // E.g. "config.ini"
... // As many patterns as you like
);
watchService.start(); // The actual watcher runs on a new thread
} catch (IOException e) {
LOGGER.error("Unable to register file change listener for " + fileName);
}
Complete code is in this repo.
I extended the solution by BullyWiiPlaza a bit, for integration with javafx.concurrent, e.g. javafx.concurrent.Taskand javafx.concurrent.Service.
Also I added possibility to track multiple files.
Task:
import javafx.concurrent.Task;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.nio.file.*;
import java.util.*;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
#Slf4j
public abstract class FileWatcherTask extends Task<Void> {
static class Entry {
private final Path folderPath;
private final String watchFile;
Entry(Path folderPath, String watchFile) {
this.folderPath = folderPath;
this.watchFile = watchFile;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry entry = (Entry) o;
return Objects.equals(folderPath, entry.folderPath) && Objects.equals(watchFile, entry.watchFile);
}
#Override
public int hashCode() {
return Objects.hash(folderPath, watchFile);
}
}
private final List<Entry> entryList;
private final Map<WatchKey, Entry> watchKeyEntryMap;
public FileWatcherTask(Iterable<String> watchFiles) {
this.entryList = new ArrayList<>();
this.watchKeyEntryMap = new LinkedHashMap<>();
for (String watchFile : watchFiles) {
Path filePath = Paths.get(watchFile);
boolean isRegularFile = Files.isRegularFile(filePath);
if (!isRegularFile) {
// Do not allow this to be a folder since we want to watch files
throw new IllegalArgumentException(watchFile + " is not a regular file");
}
// This is always a folder
Path folderPath = filePath.getParent();
// Keep this relative to the watched folder
watchFile = watchFile.replace(folderPath.toString() + File.separator, "");
Entry entry = new Entry(folderPath, watchFile);
entryList.add(entry);
log.debug("Watcher initialized for {} entries. ({})", entryList.size(), entryList.stream().map(e -> e.watchFile + "-" + e.folderPath).findFirst().orElse("<>"));
}
}
public FileWatcherTask(String... watchFiles) {
this(Arrays.asList(watchFiles));
}
public void watchFile() throws Exception {
// We obtain the file system of the Path
// FileSystem fileSystem = folderPath.getFileSystem();
// TODO: use the actual file system instead of default
FileSystem fileSystem = FileSystems.getDefault();
// We create the new WatchService using the try-with-resources block
try (WatchService service = fileSystem.newWatchService()) {
log.debug("Watching filesystem {}", fileSystem);
for (Entry e : entryList) {
// We watch for modification events
WatchKey key = e.folderPath.register(service, ENTRY_MODIFY);
watchKeyEntryMap.put(key, e);
}
// Start the infinite polling loop
while (true) {
// Wait for the next event
WatchKey watchKey = service.take();
for (Entry e : entryList) {
// Call this if the right file is involved
var hans = watchKeyEntryMap.get(watchKey);
if (hans != null) {
for (WatchEvent<?> watchEvent : watchKey.pollEvents()) {
// Get the type of the event
WatchEvent.Kind<?> kind = watchEvent.kind();
if (kind == ENTRY_MODIFY) {
Path watchEventPath = (Path) watchEvent.context();
onModified(e.watchFile);
}
if (!watchKey.reset()) {
// Exit if no longer valid
log.debug("Watch key {} was reset", watchKey);
break;
}
}
}
}
}
}
}
#Override
protected Void call() throws Exception {
watchFile();
return null;
}
public abstract void onModified(String watchFile);
}
Service:
public abstract class FileWatcherService extends Service<Void> {
private final Iterable<String> files;
public FileWatcherService(Iterable<String> files) {
this.files = files;
}
#Override
protected Task<Void> createTask() {
return new FileWatcherTask(files) {
#Override
public void onModified(String watchFile) {
FileWatcherService.this.onModified(watchFile);
}
};
}
abstract void onModified(String watchFile);
}
I want write several tests, but from a high level each of them should populate a directory structure with some files. I'd test each of these cases at least:
A single folder with a file that passes the filter.
A single folder with a file that does NOT pass the filter.
A nested folder with a file in each.
Code:
class FolderScan implements Runnable {
private String path;
private BlockingQueue<File> queue;
private CountDownLatch latch;
private File endOfWorkFile;
private List<Checker> checkers;
FolderScan(String path, BlockingQueue<File> queue, CountDownLatch latch,
File endOfWorkFile) {
this.path = path;
this.queue = queue;
this.latch = latch;
this.endOfWorkFile = endOfWorkFile;
checkers = new ArrayList<Checker>(Arrays.asList(new ExtentionsCheker(),
new ProbeContentTypeCheker(), new CharsetDetector()));
}
public FolderScan() {
}
#Override
public void run() {
findFiles(path);
queue.add(endOfWorkFile);
latch.countDown();
}
private void findFiles(String path) {
boolean checksPassed = true;
File root;
try {
root = new File(path);
File[] list = root.listFiles();
for (File currentFile : list) {
if (currentFile.isDirectory()) {
findFiles(currentFile.getAbsolutePath());
} else {
for (Checker currentChecker : checkers) {
if (!currentChecker.check(currentFile)) {
checksPassed = false;
break;
}
}
if (checksPassed)
queue.put(currentFile);
}
}
} catch (InterruptedException | RuntimeException e) {
System.out.println("Wrong input !!!");
e.printStackTrace();
}
}
}
Questions:
How to create files into each folder?
To prove that queue contains
the File objects that you expect?
The last element in queue is the
'trigger' File?
How to create files into each folder?
Extract the file IO and use a mocked repository for the tests. This means that you will have the IO somewhere else and may wish to use the below to test that.
A temp folder using the JUnit rule With a test folder you create the files to match the test.
To prove that queue contains the File objects that you expect?
.equals works well for File objects I believe.
A single folder with a file that does NOT pass the filter.
I'd pass in the blockers so I can pass in an "Always Pass" and "Always Fail" blocker.
public class TestFolderScan {
#Rule
public TemporaryFolder folder= new TemporaryFolder();
#Test
public void whenASingleFolderWithAFileThatPassesTheFilterThenItExistsInTheQueue() {
File expectedFile = folder.newFile("file.txt");
File endOfWorkFile = new File("EOW");
Queue queue = ...;
FolderScan subject = new FolderScan(folder.getRoot(), queue, new AllwaysPassesBlocker(),...);
subject.run();
expected = new Queue(expectedFile, endOfWorkFile);
assertEquals(queue, expected);
}
}