Monitoring file system activity with file-watchers
1 Quick Start
watch
watch-directories
suggest-approach
path-on-disk?
2 Synchronization
file-activity-channel
file-watcher-status-channel
file-watcher-channel-try-get
file-watcher-channel-get
3 Detecting changes without concern for root cause
apathetic-watch
4 Poll-based file monitoring
robust-watch
robust-poll-milliseconds
5 Verbose file-level monitoring
intensive-watch
7.7

Monitoring file system activity with file-watchers

Sage Gerard

 (require file-watchers) package: file-watchers

Use file-watchers to audit and react to file activity in a system.

1 Quick Start

For command-line use, use the file-watchers raco command.

  $ raco file-watchers -h # For help

  $ raco file-watchers dir # Watch given directory

  

  # Watch files and directories with a given method (methods documented below).

  $ raco file-watchers -m apathetic dir fileA fileB

  $ raco file-watchers -m robust dir fileA fileB

  $ raco file-watchers -m intensive dir fileA fileB

For programmatic use, you can apply watch to a list of targets.

(require file-watchers)
 
(define watcher (watch '("/path/to/dir" "config.json")))

By default, lists describing file activity from the watched directory will appear via displayln.

procedure

(watch [paths    
  on-activity    
  on-status    
  thread-maker])  thread?
  paths : (listof path-on-disk?) = (list (current-directory))
  on-activity : (-> list? any) = displayln
  on-status : (-> list? any) = displayln
  thread-maker : (-> path? thread?)
   = (suggest-approach #:apathetic #f)
Returns a thread that watches all given paths representing files or directories on disk. For each path, thread-maker is invoked to create a subordinate thread to monitor that path.

The thread returned from watch will wait for all subordinate threads to terminate before it itself terminates. Breaking is enabled.

thread-maker should either be one of apathetic-watch, intensive-watch, or robust-watch, or a procedure that returns a thread created using one of those procedures.

procedure

(watch-directories [directories    
  on-activity    
  on-status    
  thread-maker])  thread?
  directories : (listof directory-exists?)
   = (list (current-directory))
  on-activity : (-> list? any) = displayln
  on-status : (-> list? any) = displayln
  thread-maker : (-> path? thread?)
   = (suggest-approach #:apathetic #f)
Like watch, except the contract is restricted to directories.

NOTE: This procedure is deprecated; use watch, instead. watch-directories will be removed after January 1, 2020.

procedure

(suggest-approach #:apathetic apathetic)  procedure?

  apathetic : boolean?
Returns a file watcher procedure depending on the output of (system-type 'fs-change). If apathetic is true, apathetic-watch will be returned instead of intensive-watch in the event that file-level monitoring is supported.

If file change events are not supported on the operating system or if file-level monitoring is unavailable, then robust-watch is returned.

procedure

(path-on-disk? path)  boolean?

  path : path?
Returns #t if the path is an existing file or directory on disk.

2 Synchronization

All file monitoring occurs in at least one thread. Activity and status information are each conveyed on a dedicated asynchronous channel. For more, see Buffered Asynchronous Channels.

Each channel message is a list that starts with a symbol for the associated file monitoring method, followed by a symbol indicating the kind of activity or status reported. For example, an apathetic-watch will convey that it is watching a directory and a change was detected somewhere inside it.

'(apathetic watching /path/to/dir)

A 'watching status comes from file-watcher-status-channel, while detected file activity comes from file-activity-channel.

value

file-activity-channel : (parameter/c async-channel?)

A parameter for a channel that reports file system activity depending on the monitoring approach.

value

file-watcher-status-channel : (parameter/c async-channel?)

A parameter for a channel that reports a specific watchers status. The meaning of a status depends on how a watcher carries out its task.

procedure

(file-watcher-channel-try-get)  (or/c boolean? list?)

Returns the next available message from file-watcher-status-channel, or file-activity-channel, in that order. Returns #f if no message is available.

procedure

(file-watcher-channel-get)  (or/c boolean? list?)

Waits for and returns the next available message from file-watcher-status-channel, or file-activity-channel.

3 Detecting changes without concern for root cause

file-watcher

(apathetic-watch path)  thread?

  path : path-on-disk?
An apathetic thread watches the file, directory, or link at the given path. It will signal any activity that triggers a filesystem-change-evt. The thread will terminate when no file, directory, or link exists at the given path.

If path is a directory, apathetic-watch will monitor all files recursively, but all changes within the directory are reported as changes to path.

An apathetic watch:

The below example starts an apathetic watch thread, waits for the thread to report that it is watching "dir", then deletes "dir". The apathetic watcher thread will report that the change occurred on file-activity-channel before terminating, since "dir" was the root path for the watching thread.

(define apathetic-watcher (apathetic-watch "dir"))
 
(sync/enable-break (file-watcher-status-channel))
(delete-directory "dir")
(displayln (sync/enable-break (file-activity-channel)))
 
(thread-wait apathetic-watcher)
(displayln (thread-dead? apathetic-watcher))

4 Poll-based file monitoring

file-watcher

(robust-watch path)  thread?

  path : path-on-disk?
A robust watch operates on a polling mechanism that compares recursive listings of the path to report changes. This approach is cross-platform, but cannot detect any activity between filesystem polls.

Furthermore, robust-watch will only compare file permissions and access times, not contents.

robust-watch only reports 'add, 'change, and 'remove events on file-activity-channel. It does not report status information on file-watcher-status-channel.

A parameter for the number of milliseconds a robust watch poll should wait before comparing directory listings. This defaults to 250.

5 Verbose file-level monitoring

file-watcher

(intensive-watch path)  thread?

  path : path-on-disk?
An intensive watch dedicates a thread to each file discoverable from path, each of which monitors its file with filesystem-change-evt.

Due to the resource-hungry nature of the model, an intensive watch may warrant a dedicated custodian.

If a link file is accessed in a way that impacts the link’s target, both the link file and the target file will be marked as changed.

Status information appears on file-watcher-status-channel under the following rules:

Activity information appears on file-activity-channel under the following rules: