• Stars
    star
    233
  • Rank 172,230 (Top 4 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created about 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A cross-platform Java recursive directory watcher, with a JNA macOS watcher and Scala better-files integration

Directory Watcher

Maven

A directory watcher utility for JDK 8+ that aims to provide accurate and efficient recursive watching for Linux, macOS and Windows. In particular, this library provides a JNA-based WatchService for Mac OS X to replace the default polling-based JDK implementation (improvement tracked in JDK-7133447).

The core directory-watcher library is designed to have minimal dependencies; currently it only depends on slf4j-api (for internal logging, which can be disabled by passing a NOPLogger in the builder) and jna (for the macOS watcher implementation).

Getting started

First add the dependency for your preferred build system.

For sbt:

libraryDependencies += "io.methvin" % "directory-watcher" % directoryWatcherVersion

For maven:

<dependency>
    <groupId>io.methvin</groupId>
    <artifactId>directory-watcher</artifactId>
    <version>${directoryWatcherVersion}</version>
</dependency>

Replace the directoryWatcherVersion with the latest version (Maven), or any older version you wish to use.

Java API

Use DirectoryWatcher.builder() to build a new watcher, then use either watch() to block the current thread while watching or watchAsync() to watch in another thread. This will automatically detect Mac OS X and provide a native implementation based on the Carbon File System Events API.

Java Example

package com.example;

import io.methvin.watcher.DirectoryWatcher;

import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;

public class DirectoryWatchingUtility {

    private final Path directoryToWatch;
    private final DirectoryWatcher watcher;

    public DirectoryWatchingUtility(Path directoryToWatch) throws IOException {
        this.directoryToWatch = directoryToWatch;
        this.watcher = DirectoryWatcher.builder()
                .path(directoryToWatch) // or use paths(directoriesToWatch)
                .listener(event -> {
                    switch (event.eventType()) {
                        case CREATE: /* file created */; break;
                        case MODIFY: /* file modified */; break;
                        case DELETE: /* file deleted */; break;
                    }
                })
                // .fileHashing(false) // defaults to true
                // .logger(logger) // defaults to LoggerFactory.getLogger(DirectoryWatcher.class)
                // .watchService(watchService) // defaults based on OS to either JVM WatchService or the JNA macOS WatchService
                .build();
    }

    public void stopWatching() throws IOException {
        watcher.close();
    }

    public CompletableFuture<Void> watch() {
        // you can also use watcher.watch() to block the current thread
        return watcher.watchAsync();
    }
}

Configuration

By default, DirectoryWatcher will try to prevent duplicate events (e.g. Windows will emit duplicate modify events when a file is changed). This is done by creating a hash for every file encountered and keeping that hash in memory. This might result in slower performance, because the library has to calculate the hash of the entire file. In addition, some events may not be emitted if DirectoryWatcher encounters a file that is locked by another process while computing the hash.

To disable hashing, you can explicitly set fileHashing(false) when building your DirectoryWatcher:

DirectoryWatcher watcher = DirectoryWatcher.builder()
    .path(path)
    .listener(listener)
    .fileHashing(false)
    .build();

You can also provide a totally different hasher implementation:

DirectoryWatcher watcher = DirectoryWatcher.builder()
    .path(path)
    .listener(listener)
    // use the last modified time as a "hash" to determine if the file has changed
    .fileHasher(FileHasher.LAST_MODIFIED_TIME)
    .build();

In the above example we use the last modified time hasher. This hasher is only suitable for platforms that have at least millisecond precision in last modified times from Java. It's known to work with JDK 10+ on Macs with APFS.

better-files integration (Scala)

While the core directory-watcher library is Java only, we also provide better-files integration, which is the recommended API for Scala users. To add the library:

libraryDependencies += "io.methvin" %% "directory-watcher-better-files" % directoryWatcherVersion

The API is the same as in better-files, but using a different abstract class, RecursiveFileMonitor.

import better.files._
import io.methvin.better.files._

val myDir = File("/directory/to/watch/")
val watcher = new RecursiveFileMonitor(myDir) {
  override def onCreate(file: File, count: Int) = println(s"$file got created")
  override def onModify(file: File, count: Int) = println(s"$file got modified $count times")
  override def onDelete(file: File, count: Int) = println(s"$file got deleted")
}

import scala.concurrent.ExecutionContext.Implicits.global
watcher.start()

It also supports overriding onEvent, for example:

import java.nio.file.{Path, StandardWatchEventKinds => EventType, WatchEvent}

val watcher = new RecursiveFileMonitor(myDir) {
  override def onEvent(eventType: WatchEvent.Kind[Path], file: File, count: Int) = eventType match {
    case EventType.ENTRY_CREATE => println(s"$file got created")
    case EventType.ENTRY_MODIFY => println(s"$file got modified $count")
    case EventType.ENTRY_DELETE => println(s"$file got deleted")
  }
}

RecursiveFileMonitor also accepts an optional fileHasher parameter, which defaults to the hasher used by DirectoryWatcher by default. Set to None to disable hashing.

Note that unlike the better-files FileMonitor implementation, this implementation only supports fully recursive watching.

Implementation differences from standard WatchService

The Mac OS X implementation returns the full absolute path of the file in its change notifications, so the returned path does not need to be resolved against the WatchKey. This implementation also only watches recursively, so be aware of that if you choose to use it in another context.

On platforms that support it (Windows), the DirectoryWatcher utility and better-files watcher use ExtendedWatchEventModifier.FILE_TREE to watch recursively. On other platforms (e.g. Linux) it will watch the current directory and register a new WatchKey for subdirectories as they are added.

In addition to forwarding events from the underlying WatchService implementation, DirectoryWatcher also hashes files to determine if changes actually occurred. This tends to reduce the likelihood of duplicate or useless events and helps provide a more consistent experience across platforms.

Credits

Large parts of the Java directory-watcher code, particularly the MacOSXListeningWatchService implementation, are taken from https://github.com/takari/directory-watcher/, which is also licensed under the Apache 2 license.

License

This library is licensed under the Apache 2 license. See LICENSE for more information.

More Repositories

1

play-autoconfig

ConfigLoader generator for Play Framework 2.6+
Scala
12
star
2

play-scala-minimal-service-example

Example of a minimal Play service
Scala
9
star
3

play-json-magnolia

Play JSON typeclasses using Magnolia
Scala
5
star
4

orphan-finder

A Scala compiler plugin for finding orphaned instances
Scala
4
star
5

play-kotlin-rest-api-example

An example of a simple REST API with Play and Kotlin
Kotlin
4
star
6

play-scala-request-scoped-di-example

An example showing request-scoped components with Play, Scala and compile-time DI
Scala
4
star
7

dateformat

a simple javascript date formatter
JavaScript
3
star
8

flac2mp3

A script to convert flac files to mp3 format. There are many like it, but this one is mine.
2
star
9

sudoku

A Sudoku solver written in Scala
Scala
2
star
10

fastforward

A Scala macro to generate forwarded instances of traits
Scala
2
star
11

zipcity

A city to zip and zip to city script
JavaScript
2
star
12

play-routes-standalone-example

Test project to show how to use the RoutesCompiler without using the Play plugin
Scala
2
star
13

sprintf

a javascript string formatter
JavaScript
2
star
14

logslack

A Logback appender to write messages to Slack channels based on Markers
Scala
1
star
15

play-java-microservice-example

An example of a Play Java microservice
Java
1
star
16

sbt-tld

An sbt plugin to generate a current list of top-level domains
Scala
1
star
17

play-scalacache-module

A Play CacheApi and module for ScalaCache
Scala
1
star
18

email-reply-parser

A Scala library to parse out plain text email content
Scala
1
star
19

play-java-functional-actions

A library to provide a functional-style API for Java actions in Play
Java
1
star
20

unshredder

My simple attempt at the Instagram unshredder challenge
Ruby
1
star