watcher for file-system events;
authorwenzelm
Thu, 29 Dec 2016 15:32:13 +0100
changeset 64699 218c35908d5f
parent 64698 e022a69db531
child 64700 14deaeaa6476
watcher for file-system events;
src/Pure/General/file_watcher.scala
src/Pure/build-jars
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Pure/General/file_watcher.scala	Thu Dec 29 15:32:13 2016 +0100
@@ -0,0 +1,105 @@
+/*  Title:      Pure/General/file_watcher.scala
+    Author:     Makarius
+
+Watcher for file-system events.
+*/
+
+package isabelle
+
+
+import java.io.{File => JFile}
+import java.nio.file.FileSystems
+import java.nio.file.{WatchKey, WatchEvent, Path => JPath}
+import java.nio.file.StandardWatchEventKinds.{ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY}
+
+import scala.collection.JavaConversions
+
+
+object File_Watcher
+{
+  def apply(handle: Set[JFile] => Unit, delay: => Time = Time.seconds(0.5)): File_Watcher =
+    new File_Watcher(handle, delay)
+
+
+  /* internal state */
+
+  sealed case class State(
+    dirs: Map[JFile, WatchKey] = Map.empty,
+    changed: Set[JFile] = Set.empty)
+}
+
+class File_Watcher private(handle: Set[JFile] => Unit, delay: Time)
+{
+  private val state = Synchronized(File_Watcher.State())
+  private val watcher = FileSystems.getDefault.newWatchService()
+
+
+  /* registered directories */
+
+  def register(dir: JFile): Unit =
+    state.change(st =>
+      st.dirs.get(dir) match {
+        case Some(key) if key.isValid => st
+        case _ =>
+          val key = dir.toPath.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY)
+          st.copy(dirs = st.dirs + (dir -> key))
+      })
+
+  def deregister(dir: JFile): Unit =
+    state.change(st =>
+      st.dirs.get(dir) match {
+        case None => st
+        case Some(key) =>
+          key.cancel
+          st.copy(dirs = st.dirs - dir)
+      })
+
+
+  /* changed directory entries */
+
+  private val delay_changed = Standard_Thread.delay_last(delay)
+  {
+    val changed = state.change_result(st => (st.changed, st.copy(changed = Set.empty)))
+    handle(changed)
+  }
+
+  private val watcher_thread = Standard_Thread.fork("File_Watcher", daemon = true)
+  {
+    while (true) {
+      val key = watcher.take
+      val has_changed =
+        state.change_result(st =>
+          {
+            val (remove, changed) =
+              st.dirs.collectFirst({ case (dir, key1) if key == key1 => dir }) match {
+                case Some(dir) =>
+                  val events =
+                    JavaConversions.collectionAsScalaIterable(
+                      key.pollEvents.asInstanceOf[java.util.List[WatchEvent[JPath]]])
+                  val remove = if (key.reset) None else Some(dir)
+                  val changed =
+                    (Set.empty[JFile] /: events.iterator) {
+                      case (set, event) => set + dir.toPath.resolve(event.context).toFile
+                    }
+                  (remove, changed)
+                case None =>
+                  key.pollEvents
+                  key.reset
+                  (None, Set.empty[JFile])
+              }
+            (changed.nonEmpty, st.copy(dirs = st.dirs -- remove, changed = st.changed ++ changed))
+          })
+      if (has_changed) delay_changed.invoke()
+    }
+  }
+
+
+  /* shutdown */
+
+  def shutdown
+  {
+    watcher_thread.interrupt
+    watcher_thread.join
+    delay_changed.revoke
+  }
+}
--- a/src/Pure/build-jars	Thu Dec 29 14:43:25 2016 +0100
+++ b/src/Pure/build-jars	Thu Dec 29 15:32:13 2016 +0100
@@ -44,6 +44,7 @@
   General/date.scala
   General/exn.scala
   General/file.scala
+  General/file_watcher.scala
   General/graph.scala
   General/graph_display.scala
   General/graphics_file.scala