2013-10-20 23 views
6

私はJava 7 WatchServiceを使用して、ファイルの変更を監視しています。Java 7 watchservice getファイルのオフセットを取得する

は、ここでは、コードの少しは私がノックだ:

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; 
     } 
    } 

これが動作しているようだ、と私はファイル「changethis.txt」が変更されますがある場合の通知を取得します。

しかし、ファイルが変更されたときに通知できることに加えて、変更が行われたファイル内の場所が通知されることはありますか?

私はJavaのドキュメントを見てきましたが、何も見つかりませんでした。

これはWatchServiceを使用して可能ですか、カスタムを実装する必要がありますか?それは価値がある何のため

おかげ

+2

「WatchService」ではこのようなことはできません。 –

+0

ありがとうございます。 Java 7/NIOにはこれが可能なことはありますか? – Tony

+0

私は気づいていません。前後のクラスの独自のスキャンを実装する必要があります。 'WatchService'はこのimoには理想的ではありません。 –

答えて

4

は、私が

  • 統一表示
  • 、追加、変更と見ディレクトリ内のファイルを削除し検出することが可能であるという概念の少し証拠をハッキングしてきました各変更の差分(ファイルが追加/削除されたときのフル差分も)
  • ソースディレクトリのシャドウコピーを保持して連続的な変更を追跡します。
  • はユーザー定義のリズムで動作し(デフォルトは5秒)、短時間に小さすぎる差分を印刷しないようにします。

本番環境での障害になり、いくつかの制約があります

必要以上のサンプルコードを複雑にしないために、
  • 、サブディレクトリが(理由は、シャドウディレクトリが作成されて最初にコピーされます。深いディレクトリコピーを作成する既存の方法をリサイクルしましたが、実行時には無視されます)。再帰を避けるため、監視されているディレクトリのすぐ下のファイルのみが監視されます。
  • 外部ライブラリを使用しないという要件は満たされていません。なぜなら、私は、統合された差分作成のためにホイールを再発明することを避けたかったからです。
  • tail -fのようなファイルの末尾だけでなく、テキストファイルのどこでも変更を検出することができる最大の利点は、最大の欠点です。ファイルが変更されるたびに、完全にシャドウコピーする必要があります。プログラムは後続の変更を検出できません。だから私は非常に大きなファイルのこのソリューションをお勧めしません。

構築する方法:

<?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> 

ソースコードを(申し訳ありませんが、少し長い):

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(); 
    } 
} 

が、私は「時計を(例えば、デフォルトの設定を使用するという名前のソースディレクトリを使用することをお勧めしますエディタでテキストファイルを作成して編集するときに、コンソール出力を見て、しばらくの間それを試してみてください。これは、ソフトウェアの内部の仕組みを理解するのに役立ちます。何かがうまくいかない場合、たとえば1つの5秒リズム内でファイルが作成されますが、もう一度素早く削除されます。コピーまたは差分はありませんので、プログラムは単にスタックトレースをSystem.errに出力します。

+0

+1と良い包括的な答えをありがとう。この場合、外部差分ツールの使用方法を理解できます。私の場合、コンテンツは追加されるだけなので、差分がはるかに簡単になります。いずれにせよ、私は単にファイルのコピーを持っているというアプローチが本当に好きではありません。 – Simon

+0

さて、サイモン、あなたは質問の著者ではなく、おそらくあなたの "diffはもっと楽になるだろう"というのはタイプミスで、 diff "の代わりに" tail "を使用します。これにも解決策があります。私は、プラットフォームに依存しないようにしたいと思います(Windowsにあらかじめインストールされているdiff/tailはありません):https://github.com/dpillay/tail4j(未テスト) – kriegaex

+0

ええ、私は知っています。 .. そう? :-)コンテンツが添付されているファイルの差分は、実際は私が言うようなテールです:-)とにかく、もっと答えがあるかどうかを確認するために数日待っていますが、そうでなければ、あなたに賞金を授与します。 – Simon

3

これは私の前のファイルの変更点としての、ファイル位置の変更(diff)の別の回答です。今や幾分単純なケースは、ファイルのみが追加されることです(テール)。

どのように構築する方法:

<?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> 

あなたが見ることができるように、我々はここではApache CommonsのIOを使用しています。 (?興味のある方は、なぜスナップショットのバージョンは、XMLコメント内のリンクに従ってください。)

ソースコードを:

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(); 
    } 
} 

今、既存のファイルおよび/または新しいものを作成することに追加してみてください。すべてが標準出力に出力されます。実稼働環境では、ログファイルごとに1つずつ、複数のウィンドウまたはタブを表示することがあります。何でも...

@シモン:これは、より一般的な場合よりもあなたの状況に合っており、賞金に値するものです。 :-)

+0

ありがとう。 2つの答えの組み合わせは素晴らしいです。これを受け入れられたものにマージすることを検討することができます。 – Simon

+0

Nah、ユースケースはあまりにも異なっており、それぞれの回答はあまりにも冗長すぎます。 ;-) – kriegaex

関連する問題