Für was es wert ist, habe ich ein wenig Proof of Concept gehackt, die
- detect hinzugefügt der Lage ist, modifiziert und gelöschte Dateien in einem überwachten Verzeichnis,
- einheitliche Anzeige Diffs für jede Änderung (auch vollständige Diffs, wenn Dateien hinzugefügt/gelöscht wurden),
- Verfolgung der sukzessiven Änderungen durch eine Schattenkopie des Quellverzeichnisses,
- Arbeit in einem benutzerdefinierten Rhythmus (die Standardeinstellung ist 5 Sekunden), um nicht zu viele kleine diffs in kurzer Zeit zu drucken, sondern etwas größere einmal in einer Weile.
Es gibt mehrere Einschränkungen, die Hindernisse in Produktionsumgebungen sein würde:
- Um den Beispielcode mehr als nötig, um nicht zu erschweren, werden Verzeichnisse am Anfang kopiert, wenn das Schatten-Verzeichnis erstellt wird (weil Ich habe eine vorhandene Methode zum Erstellen einer tiefen Verzeichniskopie wiederverwendet, aber während der Laufzeit ignoriert. Nur Dateien direkt unter dem überwachten Verzeichnis werden überwacht, um Rekursionen zu vermeiden.
- Ihre Anforderung, keine externen Bibliotheken zu verwenden, wird nicht erfüllt, weil ich wirklich vermeiden wollte, das Rad für die einheitliche Diff-Erstellung neu zu erfinden.
- Der größte Vorteil dieser Lösung - es ist in der Lage, Änderungen in einer Textdatei zu erkennen, nicht nur am Ende der Datei wie
tail -f
- ist auch der größte Nachteil: Wenn eine Datei ändert, muss es vollständig Schatten kopiert werden, da die anderen Programm kann die nachfolgende Änderung nicht erkennen. Daher würde ich diese Lösung für sehr große Dateien nicht empfehlen.
Wie bauen:
<?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>
Quellcode (sorry, etwas langatmig):
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();
}
}
Ich empfehle die Standardeinstellungen zu verwenden (zB ein Quellverzeichnis verwenden den Namen „Uhr -dir ") und spielen, um mit ihm für eine Weile und beobachtete die Ausgabe der Konsole, wie Sie einige Textdateien in einem Editor erstellen und bearbeiten. Es hilft, die inneren Mechanismen der Software zu verstehen. Wenn etwas schief geht, z.B.innerhalb eines 5-Sekunden-Rhythmus wird eine Datei erstellt, aber auch schnell wieder gelöscht, es gibt nichts zu kopieren oder zu diff. Das Programm druckt also nur einen Stack-Trace auf System.err
.
So etwas ist mit 'WatchService' nicht möglich. –
Danke. Gibt es etwas in Java 7/NIO, das es könnte? – Tony
Nicht, dass ich mir dessen bewusst bin. Sie müssen vorher einen eigenen Scan der Klasse durchführen. Ein 'WatchService' wäre für dieses Imo nicht ideal. –