2013-10-20 5 views
6

मिलता है मैं परिवर्तन के लिए फ़ाइल की निगरानी के लिए जावा 7 वॉच सेवा के साथ खेल रहा हूं।जावा 7 वॉचस सर्विस को फ़ाइल परिवर्तन ऑफसेट

यहाँ कोड का एक छोटा सा मैं खटखटाया है:

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' संशोधित हो जाता है के रूप में सूचनाएं प्राप्त करें।

हालांकि, फ़ाइल बदलने पर अधिसूचित करने में सक्षम होने के अलावा, क्या संशोधन के दौरान फ़ाइल के भीतर स्थान के रूप में अधिसूचित किया जा रहा है?

मैंने जावा दस्तावेज़ों को देखा है लेकिन मुझे कुछ भी नहीं मिल रहा है।

क्या यह वॉच सेवा का उपयोग कर संभव है, या कुछ कस्टम लागू किया जाना चाहिए?

धन्यवाद

+2

ऐसी चीज़ 'वॉच सेवा' के साथ संभव नहीं है। –

+0

धन्यवाद। जावा 7/एनआईओ के भीतर कुछ भी है जो ऐसा करने में सक्षम हो सकता है? – Tony

+0

ऐसा नहीं है कि मुझे पता है। आपको पहले/बाद में कक्षा के अपने स्कैन को लागू करने की आवश्यकता होगी। इस आईएमओ के लिए 'वॉच सर्विस' आदर्श नहीं होगा। –

उत्तर

4

के लिए क्या इसके लायक है, मैं अवधारणा का एक छोटा सबूत जो

  • करने में सक्षम भी पता लगा कहा, संशोधित और एक देखा निर्देशिका में फ़ाइलों को नष्ट कर दिया हैक कर लिया है,
  • एकीकृत प्रदर्शित प्रत्येक परिवर्तन के लिए भिन्नता (फ़ाइलों को जोड़ा/हटाए जाने पर भी पूर्ण diffs),
  • स्रोत निर्देशिका की छाया प्रतिलिपि रखते हुए लगातार परिवर्तनों का ट्रैक रखते हुए,
  • उपयोगकर्ता द्वारा परिभाषित लय में काम करता है (डिफ़ॉल्ट 5 सेकंड होता है) ताकि थोड़े समय में बहुत से छोटे अंतरों को मुद्रित न किया जा सके, बल्कि थोड़ी देर में कुछ हद तक बड़ा हो।

कई सीमाएं जो उत्पादन वातावरण में बाधाओं होगा रहे हैं:

  • आदेश में आवश्यकता से अधिक नमूना कोड को मुश्किल नहीं करने के लिए, उप निर्देशिकाओं शुरुआत जब छाया निर्देशिका बनाई गई है पर कॉपी कर रहे हैं (क्योंकि मैंने एक गहरी निर्देशिका प्रतिलिपि बनाने के लिए एक मौजूदा विधि का पुनर्नवीनीकरण किया है), लेकिन रनटाइम के दौरान अनदेखा किया गया। रिकर्सन से बचने के लिए देखे गए निर्देशिका के ठीक नीचे केवल फाइलों की निगरानी की जा रही है।
  • बाहरी पुस्तकालयों का उपयोग न करने की आपकी आवश्यकता पूरी नहीं हुई है क्योंकि मैं वास्तव में एकीकृत diff निर्माण के लिए पहिया को फिर से आविष्कार करना चाहता हूं।
  • इस समाधान का सबसे बड़ा लाभ - यह टेक्स्ट फ़ाइल में कहीं भी परिवर्तनों का पता लगाने में सक्षम है, न केवल 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(); 
    } 
} 

मैं डिफ़ॉल्ट सेटिंग्स (जैसे घड़ी का उपयोग एक स्रोत नामक निर्देशिका का उपयोग करने के लिए "की सिफारिश -dir ") और थोड़ी देर के लिए इसके साथ खेलें, कंसोल आउटपुट को देखते हुए जब आप एक संपादक में कुछ टेक्स्ट फाइलें बनाते हैं और संपादित करते हैं। यह सॉफ्टवेयर के आंतरिक यांत्रिकी को समझने में मदद करता है। अगर कुछ गलत हो जाता है, उदा।एक 5 सेकंड लय के भीतर एक फ़ाइल बनाई जाती है लेकिन फिर से जल्दी से हटा दी जाती है, कॉपी या डिफाई करने के लिए कुछ भी नहीं है, इसलिए प्रोग्राम सिर्फ System.err पर एक स्टैक ट्रेस प्रिंट करेगा।

+0

+1 और एक अच्छे और व्यापक उत्तर के लिए धन्यवाद। मैं इस मामले में बाहरी diff उपकरण के उपयोग को समझ सकता हूं। मेरे मामले में, सामग्री केवल संलग्न की जाएगी, ताकि एक अंतर बहुत आसान हो। किसी भी मामले में, मुझे वास्तव में फ़ाइल की प्रतिलिपि बनाने के दृष्टिकोण को पसंद नहीं है। मुझे अभी भी उम्मीद है कि एक बेहतर समाधान है, हालांकि मुझे संदेह है कि :-) – Simon

+0

ठीक है, साइमन, आप इस सवाल का लेखक नहीं हैं, और शायद आपका "एक अंतर आसान होगा" एक टाइपो था और आप " "diff" के बजाय पूंछ "। इसके लिए एक समाधान भी है, और मुझे लगता है कि हम प्लेटफार्म स्वतंत्र रहना चाहते हैं (उदाहरण के लिए विंडोज पर कोई diff/पूंछ पूर्वस्थापित नहीं है): https://github.com/dpillay/tail4j (untested) – 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> 

आप देख सकते हैं, हम यहाँ अपाचे कॉमन्स आईओ का उपयोग करें। (क्यों एक स्नैपशॉट संस्करण यदि आप रुचि रखते हैं एक्सएमएल टिप्पणी में लिंक का पालन करें।)

स्रोत कोड:

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

अब मौजूदा फ़ाइलों और/या नए लोगों को बनाने के लिए जोड़कर प्रयास करें। सब कुछ मानक आउटपुट के लिए मुद्रित किया जाएगा। एक उत्पादन वातावरण में आप कई लॉग या फ़ाइल प्रदर्शित कर सकते हैं, प्रत्येक लॉग फ़ाइल के लिए एक। जो कुछ भी ...

@ सिमॉन: मुझे आशा है कि यह आपकी स्थिति को अधिक सामान्य मामले की तुलना में बेहतर तरीके से उपयुक्त करे और एक बकाया राशि के लायक है। :-)

+0

बहुत बहुत धन्यवाद। दो उत्तरों का संयोजन बहुत अच्छा है। आप इसे स्वीकार किए गए एक में विलय करने पर विचार कर सकते हैं। – Simon

+0

नहीं, उपयोग के मामले बहुत अलग हैं और प्रत्येक उत्तर स्वयं ही वर्बोज़ से पहले ही है। ;-) – kriegaex

संबंधित मुद्दे