-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
530 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,5 @@ pom.xml.releaseBackup | |
pom.xml.versionsBackup | ||
pom.xml.next | ||
release.properties | ||
*.iml | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# java-file-change-watcher | ||
|
||
The JavaFileChangeWatcher is a small programm (Jar File: ~10Kb) which watches a single file for changes and executes | ||
a command if the file was created or changed. | ||
|
||
The program aims to be similar to [inotifywatch](https://github.com/rvoicilas/inotify-tools) but platform independent | ||
so that it works on Windows as well. | ||
|
||
# Features | ||
|
||
- ...uses the Watcher-API introduced in Java 7 which should make it get along with low system resources. | ||
- ...after a file change was detected waits another second before executing. If another change is detected during that | ||
second start to wait another second to reduce command executions if many alterations happen to a file in a small time. | ||
|
||
# Usage | ||
|
||
java -jar filewatch.jar [-v] <fileToWatch> <commandToExecute> | ||
|
||
Remember that "commandToExecute" is not executed in a shell, so you cannot do any fancy stuff like string expansion (*). | ||
I suggest creating a script file (.bat on Windows, .sh on Linux) with your fancy things and just let this program call | ||
that script. | ||
|
||
# Alternatives | ||
|
||
The following alternatives I encountered: | ||
|
||
- [Using the PowerShell on Windows](http://blogs.technet.com/b/heyscriptingguy/archive/2004/10/11/how-can-i-automatically-run-a-script-any-time-a-file-is-added-to-a-folder.aspx) (I could not get that one to work, but I never worked with the MS Powershell before so it's probably just me) | ||
- [Belvedere](http://ca.lifehacker.com/341950/belvedere-automates-your-self+cleaning-pc) (Is Windows only and I wanted something that works on Linux as well for testing. Additionally it is not lightweight) | ||
- [when_changed](https://github.com/benblamey/when_changed) (Similar to this one, but written in C#. Maybe it works with Mono on Linux, I did not test) | ||
|
||
If you know another tool which you think may be helpful for others to find, feel free to create a pull request. | ||
|
||
# Contributing | ||
|
||
Feel free to create a pull request if you think something important is missing. Please submit tests with your code and | ||
remember that this tool is meant to be lightweight, so no huge add ons. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?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.softwertiger.filewatch</groupId> | ||
<artifactId>filewatch</artifactId> | ||
<version>1.0</version> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>3.1</version> | ||
<configuration> | ||
<source>1.8</source> | ||
<target>1.8</target> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-jar-plugin</artifactId> | ||
<configuration> | ||
<archive> | ||
<manifest> | ||
<addClasspath>true</addClasspath> | ||
<mainClass>de.softwertiger.filewatch.cli.Main</mainClass> | ||
</manifest> | ||
</archive> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.testng</groupId> | ||
<artifactId>testng</artifactId> | ||
<version>6.8.8</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mockito</groupId> | ||
<artifactId>mockito-all</artifactId> | ||
<version>1.9.5</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
33 changes: 33 additions & 0 deletions
33
src/main/java/de/softwertiger/filewatch/DelayCallMerger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package de.softwertiger.filewatch; | ||
|
||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
public class DelayCallMerger implements Runnable { | ||
private final Runnable delegate; | ||
private volatile AtomicInteger runCounter = new AtomicInteger(0); | ||
|
||
public DelayCallMerger(final Runnable delegate) { | ||
this.delegate = delegate; | ||
} | ||
|
||
@Override | ||
public void run() { | ||
int id = runCounter.incrementAndGet(); | ||
new Thread() { | ||
@Override | ||
public void run() { | ||
try { | ||
Thread.sleep(1000); | ||
} catch (InterruptedException e) { | ||
e.printStackTrace(); | ||
return; | ||
} | ||
synchronized (DelayCallMerger.this) { | ||
if (id == runCounter.get()) { | ||
delegate.run(); | ||
} | ||
} | ||
} | ||
}.start(); | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
src/main/java/de/softwertiger/filewatch/OnFileChangeRunner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package de.softwertiger.filewatch; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.FileSystems; | ||
import java.nio.file.Path; | ||
import java.nio.file.WatchEvent; | ||
import java.nio.file.WatchKey; | ||
import java.nio.file.WatchService; | ||
|
||
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; | ||
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; | ||
|
||
public class OnFileChangeRunner { | ||
private final Path watchDir; | ||
private final Path watchFile; | ||
private final WatchService watchService; | ||
|
||
private OnFileChangeRunner(final Path watchFile) throws IOException { | ||
this.watchFile = watchFile; | ||
watchDir = watchFile.getParent(); | ||
watchService = FileSystems.getDefault().newWatchService(); | ||
watchDir.register(watchService, ENTRY_CREATE, ENTRY_MODIFY); | ||
} | ||
|
||
public static OnFileChangeRunner registerForFile(final Path watchFile) throws IOException { | ||
return new OnFileChangeRunner(watchFile); | ||
} | ||
|
||
public void runOnFileChange(final Runnable runnable) { | ||
try { | ||
tryRunOnFileChange(runnable); | ||
} catch (IOException e) { | ||
throw new Error(e); | ||
} | ||
} | ||
|
||
private void tryRunOnFileChange(final Runnable runnable) throws IOException { | ||
try { | ||
final WatchKey take = watchService.take(); | ||
while (!Thread.interrupted()) { | ||
for (final WatchEvent<?> watchEvent : take.pollEvents()) { | ||
final WatchEvent<Path> ev = cast(watchEvent); | ||
if (watchFile.equals(watchDir.resolve(ev.context()))) { | ||
runnable.run(); | ||
} | ||
} | ||
} | ||
} catch (InterruptedException e) { | ||
// Ignore | ||
} | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private static <T> WatchEvent<T> cast(WatchEvent<?> event) { | ||
return (WatchEvent<T>) event; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package de.softwertiger.filewatch; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.PrintStream; | ||
import java.util.concurrent.ThreadFactory; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
public class StreamUtil { | ||
private final ThreadFactory threadFactory; | ||
private final int bufferSize; | ||
|
||
public StreamUtil() { | ||
this(new ThreadFactory() { | ||
private final AtomicInteger threadNumber = new AtomicInteger(1); | ||
@Override | ||
public Thread newThread(final Runnable r) { | ||
return new Thread(r, "stream-util-" + threadNumber); | ||
} | ||
}, 8192); | ||
} | ||
|
||
StreamUtil(final ThreadFactory threadFactory, final int bufferSize) { | ||
this.threadFactory = threadFactory; | ||
this.bufferSize = bufferSize; | ||
} | ||
|
||
public Thread forwardStream(final InputStream in, final PrintStream out) { | ||
Thread thread = threadFactory.newThread(() -> { | ||
byte[] buffer = new byte[bufferSize]; | ||
int read; | ||
try { | ||
while((read = in.read(buffer)) > -1) { | ||
out.write(buffer, 0, read); | ||
} | ||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
} | ||
}); | ||
thread.start(); | ||
return thread; | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
src/main/java/de/softwertiger/filewatch/cli/IllegalCommandLineOptionsException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package de.softwertiger.filewatch.cli; | ||
|
||
public class IllegalCommandLineOptionsException extends RuntimeException { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package de.softwertiger.filewatch.cli; | ||
|
||
import de.softwertiger.filewatch.DelayCallMerger; | ||
import de.softwertiger.filewatch.OnFileChangeRunner; | ||
import de.softwertiger.filewatch.StreamUtil; | ||
|
||
import java.io.IOException; | ||
|
||
public class Main { | ||
private static final StreamUtil streamUtil = new StreamUtil(); | ||
private final Options options; | ||
|
||
public Main(final Options options) throws IOException { | ||
this.options = options; | ||
if (options.isVerbose()) { | ||
System.out.println("Watching <" + options.getFileToWatch() + "> for changes"); | ||
} | ||
OnFileChangeRunner | ||
.registerForFile(options.getFileToWatch()) | ||
.runOnFileChange(new DelayCallMerger(this::tryRunCommand)); | ||
} | ||
|
||
public static void main(String[] args) throws Exception { | ||
try { | ||
new Main(Options.parseCommandLineOptions(args)); | ||
} catch (IllegalCommandLineOptionsException e) { | ||
System.out.println("Usage: fileWatcher [-v] <fileToWatch> <commandToExecute>"); | ||
} | ||
} | ||
|
||
private void tryRunCommand() { | ||
if (options.isVerbose()) { | ||
System.out.println("File has changed. Executing..."); | ||
} | ||
try { | ||
final Process process = Runtime.getRuntime().exec(options.getCommand()); | ||
streamUtil.forwardStream(process.getInputStream(), System.out); | ||
streamUtil.forwardStream(process.getErrorStream(), System.err); | ||
} catch (IOException e) { | ||
throw new Error(e); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package de.softwertiger.filewatch.cli; | ||
|
||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
|
||
public class Options { | ||
private final boolean verbose; | ||
private final Path fileToWatch; | ||
private final String command; | ||
|
||
private Options(final boolean verbose, final Path fileToWatch, final String command) { | ||
if (fileToWatch == null || command == null) { | ||
throw new IllegalCommandLineOptionsException(); | ||
} | ||
this.verbose = verbose; | ||
this.fileToWatch = fileToWatch; | ||
this.command = command; | ||
} | ||
|
||
public static Options parseCommandLineOptions(final String[] args) { | ||
boolean verbose = false; | ||
Path fileToWatch = null; | ||
String command = null; | ||
int unnamedPos = 0; | ||
for (final String arg : args) { | ||
if (arg.equals("-v")) { | ||
verbose = true; | ||
} else { | ||
if (unnamedPos == 0) { | ||
fileToWatch = Paths.get(System.getProperty("user.dir")).resolve(arg).normalize(); | ||
} else if (unnamedPos == 1) { | ||
command = arg; | ||
} else { | ||
throw new IllegalCommandLineOptionsException(); // too many args | ||
} | ||
++unnamedPos; | ||
} | ||
} | ||
|
||
return new Options(verbose, fileToWatch, command); | ||
} | ||
|
||
public String getCommand() { | ||
return command; | ||
} | ||
|
||
public Path getFileToWatch() { | ||
return fileToWatch; | ||
} | ||
|
||
public boolean isVerbose() { | ||
return verbose; | ||
} | ||
} |
Oops, something went wrong.