Java 7 watchservice gets file change offset

I just played with the Java 7 WatchService to monitor the file for change.

Here is some code that I beat:

WatchService watcher = FileSystems.getDefault().newWatchService(); Path path = Paths.get("c:\\testing"); path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); while (true) { WatchKey key = watcher.take(); for (WatchEvent event : key.pollEvents()) { System.out.println(event.kind() + ":" + event.context()); } boolean valid = key.reset(); if (!valid) { break; } } 

This seems to work and I get notifications when the file 'changethis.txt' changes.

However, besides the fact that you can notify me when a file changes, is there anyway a notification about the location in the file in which the modification occurred?

I looked through Java docs but I can not find anything.

Is this possible with the WatchService or should something ordinary be implemented?

thanks

+6
source share
2 answers

For what it's worth, I hacked a little evidence of a concept that is capable of

  • detect added, modified and deleted files in the monitored directory,
  • display of uniform differences for each change (also a complete difference when adding / deleting files),
  • tracking successive changes by storing a shadow copy of the source directory,
  • work in a user-defined rhythm (5 seconds by default) so as not to print too many small differences in a short period of time, and sometimes a few large ones.

There are several limitations that can be barriers in production environments:

  • In order not to complicate the sample code more than necessary, the subdirectories are copied at the beginning when creating the shadow directory (because I reworked the existing method to create a deep copy of the directory), but are ignored at runtime. Only files directly below the directory being watched are tracked to avoid recursion.
  • Your requirement not to use external libraries is not fulfilled, because I really wanted not to reinvent the wheel to create uniform differences.
  • The biggest advantage of this solution - it is able to detect changes anywhere in the text file, and not just at the end of the file, for example tail -f - is also its biggest drawback: whenever the file changes, it should be completely shadowed, because otherwise, the Program cannot detect a subsequent change. Therefore, I would not recommend this solution for very large files.

How to build:

 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.scrum-master.tools</groupId> <artifactId>SO_WatchServiceChangeLocationInFile</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>com.googlecode.java-diff-utils</groupId> <artifactId>diffutils</artifactId> <version>1.3.0</version> </dependency> </dependencies> </project> 

Source code (sorry, a little long):

 package de.scrum_master.app; import difflib.DiffUtils; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.LinkedList; import java.util.List; import static java.nio.file.StandardWatchEventKinds.*; public class FileChangeWatcher { public static final String DEFAULT_WATCH_DIR = "watch-dir"; public static final String DEFAULT_SHADOW_DIR = "shadow-dir"; public static final int DEFAULT_WATCH_INTERVAL = 5; private Path watchDir; private Path shadowDir; private int watchInterval; private WatchService watchService; public FileChangeWatcher(Path watchDir, Path shadowDir, int watchInterval) throws IOException { this.watchDir = watchDir; this.shadowDir = shadowDir; this.watchInterval = watchInterval; watchService = FileSystems.getDefault().newWatchService(); } public void run() throws InterruptedException, IOException { prepareShadowDir(); watchDir.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); while (true) { WatchKey watchKey = watchService.take(); for (WatchEvent<?> event : watchKey.pollEvents()) { Path oldFile = shadowDir.resolve((Path) event.context()); Path newFile = watchDir.resolve((Path) event.context()); List<String> oldContent; List<String> newContent; WatchEvent.Kind<?> eventType = event.kind(); if (!(Files.isDirectory(newFile) || Files.isDirectory(oldFile))) { if (eventType == ENTRY_CREATE) { if (!Files.isDirectory(newFile)) Files.createFile(oldFile); } else if (eventType == ENTRY_MODIFY) { Thread.sleep(200); oldContent = fileToLines(oldFile); newContent = fileToLines(newFile); printUnifiedDiff(newFile, oldFile, oldContent, newContent); try { Files.copy(newFile, oldFile, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { e.printStackTrace(); } } else if (eventType == ENTRY_DELETE) { try { oldContent = fileToLines(oldFile); newContent = new LinkedList<>(); printUnifiedDiff(newFile, oldFile, oldContent, newContent); Files.deleteIfExists(oldFile); } catch (Exception e) { e.printStackTrace(); } } } } watchKey.reset(); Thread.sleep(1000 * watchInterval); } } private void prepareShadowDir() throws IOException { recursiveDeleteDir(shadowDir); Runtime.getRuntime().addShutdownHook( new Thread() { @Override public void run() { try { System.out.println("Cleaning up shadow directory " + shadowDir); recursiveDeleteDir(shadowDir); } catch (IOException e) { e.printStackTrace(); } } } ); recursiveCopyDir(watchDir, shadowDir); } public static void recursiveDeleteDir(Path directory) throws IOException { if (!directory.toFile().exists()) return; Files.walkFileTree(directory, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { Files.delete(dir); return FileVisitResult.CONTINUE; } }); } public static void recursiveCopyDir(final Path sourceDir, final Path targetDir) throws IOException { Files.walkFileTree(sourceDir, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.copy(file, Paths.get(file.toString().replace(sourceDir.toString(), targetDir.toString()))); return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { Files.createDirectories(Paths.get(dir.toString().replace(sourceDir.toString(), targetDir.toString()))); return FileVisitResult.CONTINUE; } }); } private static List<String> fileToLines(Path path) throws IOException { List<String> lines = new LinkedList<>(); String line; try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) { while ((line = reader.readLine()) != null) lines.add(line); } catch (Exception e) {} return lines; } private static void printUnifiedDiff(Path oldPath, Path newPath, List<String> oldContent, List<String> newContent) { List<String> diffLines = DiffUtils.generateUnifiedDiff( newPath.toString(), oldPath.toString(), oldContent, DiffUtils.diff(oldContent, newContent), 3 ); System.out.println(); for (String diffLine : diffLines) System.out.println(diffLine); } public static void main(String[] args) throws IOException, InterruptedException { String watchDirName = args.length > 0 ? args[0] : DEFAULT_WATCH_DIR; String shadowDirName = args.length > 1 ? args[1] : DEFAULT_SHADOW_DIR; int watchInterval = args.length > 2 ? Integer.getInteger(args[2]) : DEFAULT_WATCH_INTERVAL; new FileChangeWatcher(Paths.get(watchDirName), Paths.get(shadowDirName), watchInterval).run(); } } 

I recommend that you use the default settings (for example, use the source directory called "watch-dir") and play with it for a while, watching the console output when you create and edit some text files in the editor. This helps to understand the internal mechanics of software. If something goes wrong, for example, within one 5-second rhythm, the file is created, but also quickly deleted again, there is nothing to copy or analyze, so the program simply prints the stack trace in System.err .

+5
source

Ok, here is another answer as a variant of my previous one for changes at any position of the file (diff). Now a slightly simpler case - files are only added (tail).

How to build:

 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>de.scrum-master.tools</groupId> <artifactId>SO_WatchServiceChangeLocationInFile</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <!-- Use snapshot because of the UTF-8 problem in https://issues.apache.org/jira/browse/IO-354 --> <version>2.5-SNAPSHOT</version> </dependency> </dependencies> <repositories> <repository> <id>apache.snapshots</id> <url>http://repository.apache.org/snapshots/</url> </repository> </repositories> </project> 

As you can see, we use Apache Commons IO here. (Why do I need a version of the snapshot? If you are interested, follow the link in the XML comments.)

Source:

 package de.scrum_master.app; import org.apache.commons.io.input.Tailer; import org.apache.commons.io.input.TailerListenerAdapter; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.*; import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; public class FileTailWatcher { public static final String DEFAULT_WATCH_DIR = "watch-dir"; public static final int DEFAULT_WATCH_INTERVAL = 5; private Path watchDir; private int watchInterval; private WatchService watchService; public FileTailWatcher(Path watchDir, int watchInterval) throws IOException { if (!Files.isDirectory(watchDir)) throw new IllegalArgumentException("Path '" + watchDir + "' is not a directory"); this.watchDir = watchDir; this.watchInterval = watchInterval; watchService = FileSystems.getDefault().newWatchService(); } public static class MyTailerListener extends TailerListenerAdapter { public void handle(String line) { System.out.println(line); } } public void run() throws InterruptedException, IOException { try (DirectoryStream<Path> dirEntries = Files.newDirectoryStream(watchDir)) { for (Path file : dirEntries) createTailer(file); } watchDir.register(watchService, ENTRY_CREATE); while (true) { WatchKey watchKey = watchService.take(); for (WatchEvent<?> event : watchKey.pollEvents()) createTailer(watchDir.resolve((Path) event.context())); watchKey.reset(); Thread.sleep(1000 * watchInterval); } } private Tailer createTailer(Path path) { if (Files.isDirectory(path)) return null; System.out.println("Creating tailer: " + path); return Tailer.create( path.toFile(), // File to be monitored Charset.defaultCharset(), // Character set (available since Commons IO 2.5) new MyTailerListener(), // What should happen for new tail events? 1000, // Delay between checks in ms true, // Tail from end of file, not from beginning true, // Close & reopen files in between reads, // otherwise file is locked on Windows and cannot be deleted 4096 // Read buffer size ); } public static void main(String[] args) throws IOException, InterruptedException { String watchDirName = args.length > 0 ? args[0] : DEFAULT_WATCH_DIR; int watchInterval = args.length > 2 ? Integer.getInteger(args[2]) : DEFAULT_WATCH_INTERVAL; new FileTailWatcher(Paths.get(watchDirName), watchInterval).run(); } } 

Now try adding existing files and / or creating new ones. Everything will be printed to standard output. In a production environment, you can display multiple windows or tabs, one for each log file. No difference...

@Simon: I hope this option suits you better than the more general case, and it deserves a reward. :-)

+3
source

Source: https://habr.com/ru/post/956359/


All Articles