From b35cd39bd18409ee863d1b0b2a0ff5f8471de25a Mon Sep 17 00:00:00 2001 From: Matthias Dittrich Date: Sat, 7 Apr 2018 21:44:29 +0200 Subject: [PATCH] API guidelines and fix https://github.com/fsharp/FAKE/issues/1838 --- src/app/Fake.IO.FileSystem/ChangeWatcher.fs | 185 ++++++++++---------- src/legacy/FakeLib/ChangeWatcher.fs | 13 +- 2 files changed, 99 insertions(+), 99 deletions(-) diff --git a/src/app/Fake.IO.FileSystem/ChangeWatcher.fs b/src/app/Fake.IO.FileSystem/ChangeWatcher.fs index edd89e12b89..412cadb6de9 100644 --- a/src/app/Fake.IO.FileSystem/ChangeWatcher.fs +++ b/src/app/Fake.IO.FileSystem/ChangeWatcher.fs @@ -1,8 +1,7 @@ /// This module contains helpers to react to file system events. -module Fake.IO.FileSystem.ChangeWatcher +namespace Fake.IO open System.IO -open Fake.Core open Fake.IO open System.Threading open System @@ -17,104 +16,106 @@ type FileChange = Name : string Status : FileStatus } -type WatchChangesOption = - { IncludeSubdirectories: bool } +module ChangeWatcher = -let private handleWatcherEvents (status : FileStatus) (onChange : FileChange -> unit) (e : FileSystemEventArgs) = - onChange ({ FullPath = e.FullPath - Name = e.Name - Status = status }) + type Options = + { IncludeSubdirectories: bool } + let private handleWatcherEvents (status : FileStatus) (onChange : FileChange -> unit) (e : FileSystemEventArgs) = + onChange ({ FullPath = e.FullPath + Name = e.Name + Status = status }) -/// Watches for changes in the matching files. -/// Returns an IDisposable which allows to dispose all internally used FileSystemWatchers. -/// -/// ## Parameters -/// - `onChange` - function to call when a change is detected. -/// - `fileIncludes` - The glob pattern for files to watch for changes. -/// -/// ## Sample -/// -/// Target.Create "Watch" (fun _ -> -/// use watcher = !! "c:/projects/watchDir/*.txt" |> ChangeWatcher.Run (fun changes -> -/// // do something -/// ) -/// -/// System.Console.ReadLine() |> ignore -/// -/// watcher.Dispose() // if you need to cleanup the watcher. -/// ) -/// -let runWithOptions options (onChange : FileChange seq -> unit) (fileIncludes : IGlobbingPattern) = - let dirsToWatch = fileIncludes |> GlobbingPattern.getBaseDirectoryIncludes + /// Watches for changes in the matching files. + /// Returns an IDisposable which allows to dispose all internally used FileSystemWatchers. + /// + /// ## Parameters + /// - `onChange` - function to call when a change is detected. + /// - `fileIncludes` - The glob pattern for files to watch for changes. + /// + /// ## Sample + /// + /// Target.Create "Watch" (fun _ -> + /// use watcher = !! "c:/projects/watchDir/*.txt" |> ChangeWatcher.Run (fun changes -> + /// // do something + /// ) + /// + /// System.Console.ReadLine() |> ignore + /// + /// watcher.Dispose() // if you need to cleanup the watcher. + /// ) + /// + let runWithOptions (foptions:Options -> Options) (onChange : FileChange seq -> unit) (fileIncludes : IGlobbingPattern) = + let options = foptions { IncludeSubdirectories = true } + let dirsToWatch = fileIncludes |> GlobbingPattern.getBaseDirectoryIncludes - //tracefn "dirs to watch: %A" dirsToWatch + //tracefn "dirs to watch: %A" dirsToWatch - // we collect changes in a mutable ref cell and wait for a few milliseconds to - // receive all notifications when the system sends them repetedly or sends multiple - // updates related to the same file; then we call 'onChange' with all cahnges - let unNotifiedChanges = ref List.empty - // when running 'onChange' we ignore all notifications to avoid infinite loops - let runningHandlers = ref false - let timerCallback = fun _ -> - lock unNotifiedChanges (fun () -> - if not (Seq.isEmpty !unNotifiedChanges) then - let changes = - !unNotifiedChanges - |> Seq.groupBy (fun c -> c.FullPath) - |> Seq.map (fun (name, changes) -> - changes - |> Seq.sortBy (fun c -> c.Status) - |> Seq.head) - unNotifiedChanges := [] - try - runningHandlers := true - onChange changes - finally - runningHandlers := false ) - // lazy evaluation of timer in order to only start timer once requested - let timer = Lazy(Func (fun ()-> - // NOTE: that the timer starts immidiatelly when constructed - // we could delay this by sending it how many ms it should delay - // itself - // The timer here has a period of 50 ms: - new Timer(timerCallback, Object(), 0, 50) :> IDisposable - ), LazyThreadSafetyMode.ExecutionAndPublication) - - let acumChanges (fileChange : FileChange) = - // only record the changes if we are not currently running 'onChange' handler - if not !runningHandlers && fileIncludes.IsMatch fileChange.FullPath then + // we collect changes in a mutable ref cell and wait for a few milliseconds to + // receive all notifications when the system sends them repetedly or sends multiple + // updates related to the same file; then we call 'onChange' with all cahnges + let unNotifiedChanges = ref List.empty + // when running 'onChange' we ignore all notifications to avoid infinite loops + let runningHandlers = ref false + let timerCallback = fun _ -> lock unNotifiedChanges (fun () -> - unNotifiedChanges := fileChange :: !unNotifiedChanges - // start the timer (ignores repeated calls) to trigger events in 50ms - (timer.Value |> ignore) ) + if not (Seq.isEmpty !unNotifiedChanges) then + let changes = + !unNotifiedChanges + |> Seq.groupBy (fun c -> c.FullPath) + |> Seq.map (fun (name, changes) -> + changes + |> Seq.sortBy (fun c -> c.Status) + |> Seq.head) + unNotifiedChanges := [] + try + runningHandlers := true + onChange changes + finally + runningHandlers := false ) + // lazy evaluation of timer in order to only start timer once requested + let timer = Lazy(Func (fun ()-> + // NOTE: that the timer starts immidiatelly when constructed + // we could delay this by sending it how many ms it should delay + // itself + // The timer here has a period of 50 ms: + new Timer(timerCallback, Object(), 0, 50) :> IDisposable + ), LazyThreadSafetyMode.ExecutionAndPublication) + + let acumChanges (fileChange : FileChange) = + // only record the changes if we are not currently running 'onChange' handler + if not !runningHandlers && fileIncludes.IsMatch fileChange.FullPath then + lock unNotifiedChanges (fun () -> + unNotifiedChanges := fileChange :: !unNotifiedChanges + // start the timer (ignores repeated calls) to trigger events in 50ms + (timer.Value |> ignore) ) - let watchers = - dirsToWatch |> List.map (fun dir -> - //tracefn "watching dir: %s" dir + let watchers = + dirsToWatch |> List.map (fun dir -> + //tracefn "watching dir: %s" dir - let watcher = new FileSystemWatcher(Path.getFullName dir, "*.*") - watcher.EnableRaisingEvents <- true - watcher.IncludeSubdirectories <- options.IncludeSubdirectories - watcher.Changed.Add(handleWatcherEvents Changed acumChanges) - watcher.Created.Add(handleWatcherEvents Created acumChanges) - watcher.Deleted.Add(handleWatcherEvents Deleted acumChanges) - watcher.Renamed.Add(fun (e : RenamedEventArgs) -> - acumChanges { FullPath = e.OldFullPath - Name = e.OldName - Status = Deleted } - acumChanges { FullPath = e.FullPath - Name = e.Name - Status = Created }) - watcher) + let watcher = new FileSystemWatcher(Path.getFullName dir, "*.*") + watcher.EnableRaisingEvents <- true + watcher.IncludeSubdirectories <- options.IncludeSubdirectories + watcher.Changed.Add(handleWatcherEvents Changed acumChanges) + watcher.Created.Add(handleWatcherEvents Created acumChanges) + watcher.Deleted.Add(handleWatcherEvents Deleted acumChanges) + watcher.Renamed.Add(fun (e : RenamedEventArgs) -> + acumChanges { FullPath = e.OldFullPath + Name = e.OldName + Status = Deleted } + acumChanges { FullPath = e.FullPath + Name = e.Name + Status = Created }) + watcher) - { new System.IDisposable with - member this.Dispose() = - for watcher in watchers do - watcher.EnableRaisingEvents <- false - watcher.Dispose() - // only dispose the timer if it has been constructed - if timer.IsValueCreated then timer.Value.Dispose() } + { new System.IDisposable with + member this.Dispose() = + for watcher in watchers do + watcher.EnableRaisingEvents <- false + watcher.Dispose() + // only dispose the timer if it has been constructed + if timer.IsValueCreated then timer.Value.Dispose() } -let run (onChange : FileChange seq -> unit) (fileIncludes : IGlobbingPattern) = runWithOptions { IncludeSubdirectories = true } onChange fileIncludes + let run (onChange : FileChange seq -> unit) (fileIncludes : IGlobbingPattern) = runWithOptions id onChange fileIncludes diff --git a/src/legacy/FakeLib/ChangeWatcher.fs b/src/legacy/FakeLib/ChangeWatcher.fs index b439190545b..2ec0049a047 100644 --- a/src/legacy/FakeLib/ChangeWatcher.fs +++ b/src/legacy/FakeLib/ChangeWatcher.fs @@ -1,23 +1,23 @@ [] /// This module contains helpers to react to file system events. -[] +[] module Fake.ChangeWatcher open System.IO -[] +[] type FileStatus = | Deleted | Created | Changed -[] +[] type FileChange = { FullPath : string Name : string Status : FileStatus } -[] +[] type WatchChangesOption = { IncludeSubdirectories: bool } @@ -56,7 +56,7 @@ let private calcDirsToWatch fileIncludes = /// watcher.Dispose() // if you need to cleanup the watches. /// ) /// -[] +[] let WatchChangesWithOptions options (onChange : FileChange seq -> unit) (fileIncludes : FileIncludes) = let dirsToWatch = fileIncludes |> calcDirsToWatch @@ -122,6 +122,5 @@ let WatchChangesWithOptions options (onChange : FileChange seq -> unit) (fileInc watcher.Dispose() timer.Dispose() } - -[] +[] let WatchChanges = WatchChangesWithOptions { IncludeSubdirectories = true }