Modules

File_Watcher

moduleSource
macOSLinuxWindows

#import "File_Watcher";

Being told when files in a folder are created, changed, moved or removed.

A File_Watcher watches folders (and, by default, everything below them) through the operating system's own notifications: inotify on Linux, FSEvents on macOS and ReadDirectoryChangesW on Windows. Nothing runs in the background for you: call process_changes from your own loop and it calls your callback for each change that has settled. Changes to one path within a short window are merged into one File_Change with the events combined. Other targets do not have this module.

Example
on_change :: (watcher: *File_Watcher(void), change: *File_Change, data: *void) {}

watcher: File_Watcher;
assert(!init(*watcher, on_change, merge_window_seconds = -1)); // refused, nothing to release

Doesn't run in the browser (skipped on wasm).

Procedures#

(watcher: *$T/File_Watcher, callback: T.File_Watcher_Callback, user_data: *T.User_Data = null, events_to_watch: File_Change_Event = .ALL_EVENTS, merge_window_seconds: float = 0.1, watch_recursively := true, verbose := false, event_buffer_size: s32 = DEFAULT_BUFFER_SIZE) -> success: bool

Sets up watcher and returns whether that worked. Call it on an empty watcher, once; afterwards add folders with add_directories.

on_change :: (watcher: *File_Watcher(void), change: *File_Change, data: *void) {}

watcher: File_Watcher;
assert(!init(*watcher, on_change, merge_window_seconds = -1));
assert(!init(*watcher, on_change, event_buffer_size = 0));
deinit(*watcher); // nothing was set up, so nothing to release

Doesn't run in the browser (skipped on wasm).

(watcher: *$T/File_Watcher, dir_paths: ..string) -> bool

Starts watching each folder in dir_paths and returns true when all of them were added. It stops at the first one that cannot be watched (it is missing, is not a folder, or the system is out of watches) and returns false; the ones before it stay watched. The failure is logged through context.logger.

(watcher: *File_Watcher) -> (changed: bool, needs_wait: bool, wait_seconds: float64)

Collects what the system has reported, calls the callback for every change whose merge window has passed, and returns (changed, needs_wait, wait_seconds): whether any callback ran, whether more may come, and how long to wait before the next call in the worst case.

deinit2 overloads
(watcher: *$T/File_Watcher)

Stops watching and frees everything the watcher holds, including changes that were found but not yet delivered, and leaves watcher empty: it can be set up again with init, and a second deinit does nothing. Call it once for each successful init. It is safe on a watcher that was never set up.

Types#

:: enum_flags

What happened to a file, as flags that can be combined: change.events & .MODIFIED != 0 asks whether one is set.

:: struct

One report from a watcher: the full_path that changed, the events that happened to it, and time_of_last_change.

:: struct

Watches folders for changes and calls a callback for them. User_Data is the type of the pointer handed to the callback; the default void is for a watcher with none.

#import "Extensions/Environment";
#import "File";
#import "String";

Seen :: struct {
    found: bool;
}

on_change :: (watcher: *File_Watcher(Seen), change: *File_Change, seen: *Seen) {
    if path_filename(change.full_path) == "jai-doc-watched.txt" && (change.events & (.ADDED | .MODIFIED)) != 0 seen.found = true;
}

main :: () {
    base := environment_variable("TMPDIR");
    if base == "" base = "/tmp";
    root := tprint("%/jai-doc-file-watcher", base);
    file := tprint("%/jai-doc-watched.txt", root);
    assert(make_directory_if_it_does_not_exist(root));
    defer {
        file_delete(file);
        delete_directory(root);
    }

    seen: Seen;
    watcher: File_Watcher(Seen);
    assert(init(*watcher, on_change, *seen));
    defer deinit(*watcher);
    assert(add_directories(*watcher, root));

    assert(write_entire_file(file, "hello"));
    for 0..249 { // up to five seconds for the system to report it
        process_changes(*watcher);
        if seen.found break;
        sleep_milliseconds(20);
    }

    assert(seen.found);
}

This one is only checked to compile, not run.

Constants#