diff --git a/os/watch/src/ErrorResponse.scala b/os/watch/src/ErrorResponse.scala new file mode 100644 index 00000000..7751c936 --- /dev/null +++ b/os/watch/src/ErrorResponse.scala @@ -0,0 +1,18 @@ +package os.watch + +// How to react to service errors +sealed trait ErrorResponse + +object ErrorResponse { + // close this instnace of the watch service + case object Close extends ErrorResponse + + // Other possibilities + //case object Continue extends ErrorResponse // the caller fixed the state + //case class Restart(paths: Seq[os.Path]) extends ErrorResponse // the caller knows + + def defaultHandler(e: WatchError): ErrorResponse = { + println(e) + Close + } +} diff --git a/os/watch/src/WatchError.scala b/os/watch/src/WatchError.scala new file mode 100644 index 00000000..4c5b879f --- /dev/null +++ b/os/watch/src/WatchError.scala @@ -0,0 +1,6 @@ +package os.watch + +sealed trait WatchError + +case object Overflow extends WatchError +case class InternalError(t: Throwable) extends WatchError diff --git a/os/watch/src/WatchServiceWatcher.scala b/os/watch/src/WatchServiceWatcher.scala index 564d33ea..a179d81f 100644 --- a/os/watch/src/WatchServiceWatcher.scala +++ b/os/watch/src/WatchServiceWatcher.scala @@ -11,9 +11,13 @@ import com.sun.nio.file.SensitivityWatchEventModifier import scala.collection.mutable import collection.JavaConverters._ +import ErrorResponse.{Close,defaultHandler} + class WatchServiceWatcher(roots: Seq[os.Path], onEvent: Set[os.Path] => Unit, - logger: (String, Any) => Unit = (_, _) => ()) extends Watcher{ + logger: (String, Any) => Unit = (_, _) => (), + onError: WatchError => ErrorResponse = defaultHandler + ) extends Watcher{ val nioWatchService = FileSystems.getDefault.newWatchService() val currentlyWatchedPaths = mutable.Map.empty[os.Path, WatchKey] @@ -111,6 +115,10 @@ class WatchServiceWatcher(roots: Seq[os.Path], case e: ClosedWatchServiceException => println("Watcher closed, exiting: " + e) isRunning.set(false) + case e: Throwable => + onError(InternalError(e)) match { + case Close => isRunning.set(false) + } } } diff --git a/os/watch/src/package.scala b/os/watch/src/package.scala index 67a30e47..114bc81b 100644 --- a/os/watch/src/package.scala +++ b/os/watch/src/package.scala @@ -1,6 +1,7 @@ package os package object watch{ + /** * Efficiently watches the given `roots` folders for changes. Any time the * filesystem is modified within those folders, the `onEvent` callback is @@ -29,9 +30,11 @@ package object watch{ */ def watch(roots: Seq[os.Path], onEvent: Set[os.Path] => Unit, - logger: (String, Any) => Unit = (_, _) => ()): AutoCloseable = { + logger: (String, Any) => Unit = (_, _) => (), + onError: WatchError => ErrorResponse = ErrorResponse.defaultHandler + ): AutoCloseable = { val watcher = System.getProperty("os.name") match{ - case "Linux" => new os.watch.WatchServiceWatcher(roots, onEvent, logger) + case "Linux" => new os.watch.WatchServiceWatcher(roots, onEvent, logger, onError) case "Mac OS X" => new os.watch.FSEventsWatcher(roots, onEvent, logger, 0.05) case osName => throw new Exception(s"watch not supported on operating system: $osName") } diff --git a/readme.md b/readme.md index e48a07b5..179f31dc 100644 --- a/readme.md +++ b/readme.md @@ -1603,7 +1603,10 @@ sha.stdout.trim ==> "acc142175fa520a1cb2be5b97cbbe9bea092e8bba3fe2e95afa64561590 #### os.watch.watch ```scala -os.watch.watch(roots: Seq[os.Path], onEvent: Set[os.Path] => Unit): Unit +os.watch.watch(roots: Seq[os.Path], + onEvent: Set[os.Path] => Unit, + onError: os.watch.WatchError => os.watch.ErrorResponse = os.watch.ErrorResponse.defaultHandler +): Unit ``` ```scala // SBT @@ -1637,6 +1640,20 @@ changes happening within the watched roots folder, apart from the path at which the change happened. It is up to the `onEvent` handler to query the filesystem and figure out what happened, and what it wants to do. +Possible errors (passed to the `onError` callback) + +- `WatchError.Overflow` The system is producing events faster + than the application is able to handle them. + +- `WatchError.InternalError(e: Throwable)` For example: removing a drive + with watched paths. + +Error responses: + +- `ErrorResponse.Close`. Terminate this instance of the watch service + +The default error handler prints an error message and returns `ErrorResponse.Close` + Here is an example of use from the Ammonite REPL: ```scala