Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix WatchChanges on Mac, fix Dispose, improve Timer usage #799

Merged
merged 1 commit into from
May 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 27 additions & 19 deletions src/app/FakeLib/ChangeWatcher.fs
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,46 @@ let WatchChanges (onChange : FileChange seq -> unit) (fileIncludes : FileInclude
dirsToWatch
|> Seq.exists (fun p -> p.StartsWith d && p <> d)
|> not)

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<FileChange>

let acumChanges (fileChange : FileChange) =
if fileIncludes.IsMatch fileChange.FullPath then
lock unNotifiedChanges (fun () -> unNotifiedChanges := [ fileChange ] @ !unNotifiedChanges)

let timer = new System.Timers.Timer(5.0)
// when running 'onChange' we ignore all notifications to avoid infinite loops
let runningHandlers = ref false

let timer = new System.Timers.Timer(50.0)
timer.AutoReset <- false
timer.Elapsed.Add(fun _ ->
lock unNotifiedChanges (fun () ->
if !unNotifiedChanges
|> Seq.length
> 0 then
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 := List.empty<FileChange>
onChange changes))
unNotifiedChanges := []
try
runningHandlers := true
onChange changes
finally
runningHandlers := false ))

tracefn "dirs to watch: %A" dirsToWatch
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:System.Timers.Timer).Start() )

let watchers =
dirsToWatch |> Seq.map (fun dir ->
dirsToWatch |> List.ofSeq |> List.map (fun dir ->
tracefn "watching dir: %s" dir

let watcher = new FileSystemWatcher(FullName dir, "*.*")
watcher.EnableRaisingEvents <- true
watcher.IncludeSubdirectories <- true
Expand All @@ -88,11 +101,6 @@ let WatchChanges (onChange : FileChange seq -> unit) (fileIncludes : FileInclude
Name = e.Name
Status = Created })
watcher)
watchers
|> Seq.length
|> ignore //force iteration

timer.Start()

{ new System.IDisposable with
member this.Dispose() =
Expand Down
30 changes: 21 additions & 9 deletions src/app/FakeLib/Globbing/Globbing.fs
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,29 @@ let inline private normalizeOutputPath (p : string) =
.TrimEnd(Path.DirectorySeparatorChar)

let internal getRoot (baseDirectory : string) (pattern : string) =
let baseDirectory = (normalizePath baseDirectory)
let baseDirectory = normalizePath baseDirectory
let normPattern = normalizePath pattern

let patternParts = normPattern.Split([| '/'; '\\' |], StringSplitOptions.RemoveEmptyEntries)
let patternPathParts =
patternParts
|> Seq.takeWhile(fun p -> not (p.Contains("*")))
|> Seq.toArray

let globRoot =
// If we did not find any "*", then drop the last bit (it is a file name, not a pattern)
( if patternPathParts.Length = patternParts.Length then
patternPathParts.[0 .. patternPathParts.Length-2]
else patternPathParts )
|> String.concat (Path.DirectorySeparatorChar.ToString())

let globRoot =
(normalizePath pattern).Split([| '/'; '\\' |], StringSplitOptions.RemoveEmptyEntries) |>
Seq.takeWhile(fun p -> not (p.Contains("*"))) |>
String.concat(Path.DirectorySeparatorChar.ToString())

if Path.IsPathRooted globRoot then
globRoot
else
Path.Combine(baseDirectory, globRoot)
// If we dropped "/" from the beginning of the path in the 'Split' call, put it back!
if normPattern.StartsWith("/") then "/" + globRoot
else globRoot

if Path.IsPathRooted globRoot then globRoot
else Path.Combine(baseDirectory, globRoot)

let internal search (baseDir : string) (input : string) =
let baseDir = normalizePath baseDir
Expand Down