| author | wenzelm | 
| Tue, 22 Oct 2024 12:45:38 +0200 | |
| changeset 81229 | e18600daa904 | 
| parent 75394 | 42267c650205 | 
| permissions | -rw-r--r-- | 
| 64699 | 1 | /* Title: Pure/General/file_watcher.scala | 
| 2 | Author: Makarius | |
| 3 | ||
| 4 | Watcher for file-system events. | |
| 5 | */ | |
| 6 | ||
| 7 | package isabelle | |
| 8 | ||
| 9 | ||
| 73909 | 10 | import java.util.{List => JList}
 | 
| 64699 | 11 | import java.io.{File => JFile}
 | 
| 12 | import java.nio.file.FileSystems | |
| 13 | import java.nio.file.{WatchKey, WatchEvent, Path => JPath}
 | |
| 14 | import java.nio.file.StandardWatchEventKinds.{ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY}
 | |
| 15 | ||
| 73353 | 16 | import scala.jdk.CollectionConverters._ | 
| 64699 | 17 | |
| 18 | ||
| 75393 | 19 | class File_Watcher private[File_Watcher] {
 | 
| 20 | // dummy template | |
| 73340 | 21 |   def register(dir: JFile): Unit = {}
 | 
| 22 |   def register_parent(file: JFile): Unit = {}
 | |
| 23 |   def deregister(dir: JFile): Unit = {}
 | |
| 24 |   def purge(retain: Set[JFile]): Unit = {}
 | |
| 25 |   def shutdown(): Unit = {}
 | |
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 26 | } | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 27 | |
| 75393 | 28 | object File_Watcher {
 | 
| 29 |   val none: File_Watcher = new File_Watcher {
 | |
| 64859 | 30 | override def toString: String = "File_Watcher.none" | 
| 31 | } | |
| 64858 | 32 | |
| 64699 | 33 | def apply(handle: Set[JFile] => Unit, delay: => Time = Time.seconds(0.5)): File_Watcher = | 
| 64858 | 34 | if (Platform.is_windows) none else new Impl(handle, delay) | 
| 64699 | 35 | |
| 36 | ||
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 37 | /* proper implementation */ | 
| 64699 | 38 | |
| 39 | sealed case class State( | |
| 40 | dirs: Map[JFile, WatchKey] = Map.empty, | |
| 41 | changed: Set[JFile] = Set.empty) | |
| 42 | ||
| 75393 | 43 |   class Impl private[File_Watcher](handle: Set[JFile] => Unit, delay: Time) extends File_Watcher {
 | 
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 44 | private val state = Synchronized(File_Watcher.State()) | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 45 | private val watcher = FileSystems.getDefault.newWatchService() | 
| 64699 | 46 | |
| 64859 | 47 | override def toString: String = | 
| 48 |       state.value.dirs.keySet.mkString("File_Watcher(", ", ", ")")
 | |
| 49 | ||
| 64699 | 50 | |
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 51 | /* registered directories */ | 
| 64699 | 52 | |
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 53 | override def register(dir: JFile): Unit = | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 54 | state.change(st => | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 55 |         st.dirs.get(dir) match {
 | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 56 | case Some(key) if key.isValid => st | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 57 | case _ => | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 58 | val key = dir.toPath.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY) | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 59 | st.copy(dirs = st.dirs + (dir -> key)) | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 60 | }) | 
| 64699 | 61 | |
| 75393 | 62 |     override def register_parent(file: JFile): Unit = {
 | 
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 63 | val dir = file.getParentFile | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 64 | if (dir != null && dir.isDirectory) register(dir) | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 65 | } | 
| 64782 | 66 | |
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 67 | override def deregister(dir: JFile): Unit = | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 68 | state.change(st => | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 69 |         st.dirs.get(dir) match {
 | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 70 | case None => st | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 71 | case Some(key) => | 
| 73367 | 72 | key.cancel() | 
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 73 | st.copy(dirs = st.dirs - dir) | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 74 | }) | 
| 64699 | 75 | |
| 64867 | 76 | override def purge(retain: Set[JFile]): Unit = | 
| 77 | state.change(st => | |
| 78 | st.copy(dirs = st.dirs -- | |
| 73367 | 79 |           (for ((dir, key) <- st.dirs.iterator if !retain(dir)) yield { key.cancel(); dir })))
 | 
| 64867 | 80 | |
| 64699 | 81 | |
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 82 | /* changed directory entries */ | 
| 64699 | 83 | |
| 75393 | 84 |     private val delay_changed = Delay.last(delay) {
 | 
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 85 | val changed = state.change_result(st => (st.changed, st.copy(changed = Set.empty))) | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 86 | handle(changed) | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 87 | } | 
| 64699 | 88 | |
| 75393 | 89 |     private val watcher_thread = Isabelle_Thread.fork(name = "file_watcher", daemon = true) {
 | 
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 90 |       try {
 | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 91 |         while (true) {
 | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 92 | val key = watcher.take | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 93 | val has_changed = | 
| 75394 | 94 |             state.change_result { st =>
 | 
| 95 | val (remove, changed) = | |
| 96 |                 st.dirs.collectFirst({ case (dir, key1) if key == key1 => dir }) match {
 | |
| 97 | case Some(dir) => | |
| 98 | val events: Iterable[WatchEvent[JPath]] = | |
| 99 | key.pollEvents.asInstanceOf[JList[WatchEvent[JPath]]].asScala | |
| 100 | val remove = if (key.reset) None else Some(dir) | |
| 101 | val changed = | |
| 102 |                       events.iterator.foldLeft(Set.empty[JFile]) {
 | |
| 103 | case (set, event) => set + dir.toPath.resolve(event.context).toFile | |
| 104 | } | |
| 105 | (remove, changed) | |
| 106 | case None => | |
| 107 | key.pollEvents | |
| 108 | key.reset | |
| 109 | (None, Set.empty[JFile]) | |
| 110 | } | |
| 111 | (changed.nonEmpty, st.copy(dirs = st.dirs -- remove, changed = st.changed ++ changed)) | |
| 112 | } | |
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 113 | if (has_changed) delay_changed.invoke() | 
| 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 114 | } | 
| 64700 | 115 | } | 
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 116 |       catch { case Exn.Interrupt() => }
 | 
| 64699 | 117 | } | 
| 118 | ||
| 119 | ||
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 120 | /* shutdown */ | 
| 64699 | 121 | |
| 75393 | 122 |     override def shutdown(): Unit = {
 | 
| 73367 | 123 | watcher_thread.interrupt() | 
| 74140 | 124 | watcher_thread.join() | 
| 73367 | 125 | delay_changed.revoke() | 
| 64807 
7d556bb6046b
dummy File_Watcher for Windows (spurious crashes seen on Windows 7);
 wenzelm parents: 
64782diff
changeset | 126 | } | 
| 64699 | 127 | } | 
| 128 | } |