1.0.0-alpha.0 • Published 5 years ago

growth-watch v1.0.0-alpha.0

Weekly downloads
-
License
MIT
Repository
-
Last release
5 years ago

growth-watch

A file tree watcher that progressively adds directories to watch.

  • Expanding/collapsing directories to watch
  • It automatically collapses expanded directories on unlink/deletion
  • Events are buffered so you can apply batch updates to the UI

Motivation

Reflecting realtime status of the filesystem is crucial when you want to make a tree-like file explorer UI in a desktop application. Filesystem watchers can be used to detect any changes on the client's filesystem. By using them, you can apply the changes to the UI on the fly in addition to showing the initial files. The easiest way to implement this functionality is creating a watcher that observes the target directory and its all descendant directories recursively.

However, sometimes it may take a significant amount of time to perform this operation due to too many files and directories underneath the target directory. So, it is a good idea to avoid watching those directories that aren't displayed on the file tree UI yet. And as the user opens the directory, it should start watching it.

growth-watch is a wrapper of fs.watch and aims to provide an API to gradually create filesystem watchers on demand. It stores the list of files and directories on the watched (expanded) directories and emits update events further on.

This package aims for running on GUI platform with node.js, Electron for example, and the requirement to deal with many items under the root directory in order to avoid watching all of sub-directories unnecessarily.

Installation

$ npm install growth-watch --save

Usage

In growth-watch, there are two ways of listening to the events: subscribing the individual events, and using buffered events, which is a group of events occured recently. Basically, you may want to use rather buffered events so you can do batch changes on the UI.

import { TreeWatcher } from "growth-watch";

// Create a watcher instance
const watcher = new TreeWatcher(targetDir);

watcher.on("buffer", events => {
    events.forEach( event => {

        switch ( event.type ) {
            case "add":
                // ...
                break;

            case "change":
                // ...
                break;

            case "remove":
                // ...
                break;

            case "expand":
                // ...
                break;

            case "collapse":
                // ...
                break;
        }

    } );
});

// When the user clicks a directory on the UI
watcher.toggleExpansion(dir);

// Teardown the watcher
watcher.dispose();

By default, buffered events are collected during 50 milliseconds and after that they will be emitted as a group of events.

API

class: TreeWatcher

import { TreeWatcher } from "growth-watch";

Methods

NOTE:

The path argument passed into TreeWatcher method other than constructor must be the relative path from the root directory. Although, both posix-style (/) and windows-style (\) path separator are acceptable, we recommend you to stick with the platform-specific style all across your code base.

constructor
new TreeWatcher(rootDir: string, options?: TreeWatcherOptions);

Creates a TreeWatcher instance.

  • rootDir: The root directory on which the TreeWatcher watches.
  • options (optional)
    • options.throttleDelay (optional)
      • The duration it takes to flush the buffered events, in ms.
      • Default: 50 ms
const watcher = new TreeWatcher(targetDir, {
    throttleDelay: 50,
});
dispose
dispose(): void;

Disposes the watcher instance. A disposed watcher is no longer usable.

waitRootForReady
waitRootForReady(): Promise<void>;

Returns a promise that resolves when the initial scan of the root directory has been completed.

expand
expand(path: string): Promise<void>;
  • path: The relative path of the directory to expand.

Expands the directory. The directory must be represented in any directories that already expanded. Otherwise it throws an error.

Returns a promise that resolves when the initial scan of the directory has been completed.

Calling this method results in an "expand" event and subsequently a "ready" event to be emitted.

collapse
collapse(path: string): void;
  • path: The relative path of the directory to collapse.

Collapse the directory. The directory must be already expanded. Otherwire it throws an error.

Calling this method results in a "close" event and then "collapse" event to be emitted immediately.

toggleExpansion
toggleExpansion(path: string): Promise<void>;

Expands the directory if it is appeared in any directories that already expanded, or collapses it if already expanded.

Returns a promise that resolves when the initial scan of the directory has been completed if it's being expanded. Otherwise it immediately resolves.

expandAll
expandAll(): Promise<void>;

Expands all directories recursively. Calling this method results no effects when the initial scan of the root directory is not completed yet.

Returns a promise that resolves when the all of initial scan has been completed.

// Let the watcher to grab the child directories of the root first
await watcher.waitRootForReady();

// Then call to expand all
watcher.expandAll();
isExpandable
isExpandable(path: string): boolean;

Returns whether the directory is currently expandable.

isCollapsable
isCollapsable(path: string): boolean;

Returns whether the directory is currently collapsable.

getExpandables
getExpandables(): string[];

Returns a list of expandable directories.

getCollapsables
getCollapsables(): string[];

Returns a list of collapsable directories.

getCollapsablesAt
getCollapsablesAt(path: string): string[];

Returns a list of collapsable directories that are also direct children of the given directory.

on
on(eventType: string, callbackFn: (event: any) => void): Disposable;

Registers the event handler for the event type. You can unregister it by calling dispose() method of the returned object.

const handleError = () => { /* ... */ };
const subscription = watcher.on("error", handleError);

// Unregistring the event handler
subscription.dispose();
once
once(eventType: string, callbackFn: (event: any) => void): Disposable;

Registers the event handler for the event type. The event handler registered via this method will be unregistered once it is invoked.

Events

NOTE:

The path property in event object is described in platform-specific style (forward-slash in Linux/Max or backward-slash in Windows)

add
watcher.on("add", (event) => { /* ... */ });
  • event
    • type: "add"
    • path: string
    • stats: fs.Stats
    • isDirectory: boolean
    • isInitial: boolean

Emitted when:

  • Initial scan finds a file or a directory existing in the expanded directory
  • A file or a directory has been created on a watched directory
change
watcher.on("change", (event) => { /* ... */ });
  • event
    • type: "change"
    • path: string
    • stats: fs.Stats
    • isDirectory: false

Emitted when a file has been updated.

remove
watcher.on("remove", (event) => { /* ... */ });
  • event
    • type: "remove"
    • path: string
    • isDirectory: boolean

Emitted when:

  • A file or a directory has been deleted
  • A watched directory that contains some files and directories has been collapsed
expand
watcher.on("expand", (event) => { /* ... */ });
  • event
    • type: "expand"
    • path: string
    • isExpanded: true

Emitted when a directory has been expanded.

collapse
watcher.on("collapse", (event) => { /* ... */ });
  • event
    • type: "collapsed"
    • path: string
    • isExpanded: false

Emitted when a directory has been collapsed.

ready
watcher.on("ready", (event) => { /* ... */ });
  • event
    • type: "ready"
    • path: string

Emitted when the initial scan of the expanded directory has been completed. Once this event occurs, any "add" event with isInitial: true will never be emitted unless it gets collapsed and then re-expanded.

close
watcher.on("close", (event) => { /* ... */ });
  • event
    • type: "close"
    • path: string

Emitted when a single filesystem watcher has been closed manually (via collapse() method) or by the filesystem, that is the watched directory has been deleted.

error
watcher.on("error", (err) => { /* ... */ });
  • error: Error

Emitted when a filesystem watcher has been failed. It also will be emitted when one of the watched directories has been deleted, along with "close" and "collapse" event.

childError
watcher.on("childError", (event) => { /* ... */ });
  • error: Error

Emitted when a filesystem watcher failed to retrieve a fs.Stats for a file or a directory.

buffer
watcher.on("buffer", (events) => { /* ... */ });
  • events: Array<Event>

Emitted with buffered events. All event types except error and childError will be buffered, and flushed when certain period of time (throttleDelay) has been elapsed after the first event in the buffer window was emitted.

Related projects

growth-watch is more forcused on the UI interactions rather than watching filesystem. If you want to just observe directories, we recommend you to use other filesystem watcher libraries because of reliability and customizability.

And if you want to filter contents received in your event handler, consider to use anymatch, which accepts glob patterns, regular expressions, and predicate functions to determine certain path should be ignored or not.

watcher.on("add", event => {
    if (anymatch(ignoreMatchers, event.path)) {
        return;
    }

    // for example
    myApp.addFileOnUI(event.path, event.isDirectory);
});