Skip to content

Commit

Permalink
Refactor; use LaunchAgent service rather than Login Items + .app shim
Browse files Browse the repository at this point in the history
  • Loading branch information
chbrown committed Jan 14, 2015
1 parent ec4d56f commit 6dedf1b
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 270 deletions.
2 changes: 0 additions & 2 deletions .gitignore

This file was deleted.

8 changes: 0 additions & 8 deletions FSChange.app/Contents/Info.plist

This file was deleted.

3 changes: 0 additions & 3 deletions FSChange.app/Contents/MacOS/FSChange

This file was deleted.

61 changes: 61 additions & 0 deletions FileWatcher.js
@@ -0,0 +1,61 @@
/*jslint node: true */
var util = require('util-enhanced');
var fs = require('fs');
var logger = require('winston');

/** new FileWatcher(filepath: string, delay: number, onchange: ())
Watch a filepath for changes, calling `onchange` when the file changes.
Mostly a wrapper around fs.watch / fs.FSWatcher, but with some debounce.
delay
Milliseconds to wait before calling onchange again. A delay of 0 means
onchange will be called immediately every time the file changes (no debounce).
*/
function FileWatcher(filepath, delay, onchange) {
this.filepath = filepath;
this.onchange = onchange;
this.delay = delay;
}

/** FileWatcher#start()
Start the underlying watcher.
Debounces for a period of `delay` seconds; executes on the immediate end.
*/
FileWatcher.prototype.start = function() {
var self = this;
var last_called = -1;

this.fs_watcher = fs.watch(this.filepath, {persistent: true})
.on('change', function(event, filename) {
// filename may not actually be supplied
// TODO: check last modified stats?
// if (curr.mtime.valueOf() != prev.mtime.valueOf() ||
// curr.ctime.valueOf() != prev.ctime.valueOf()) {
var since_last = Date.now() - last_called;
logger.debug('Saw %s on %s (%dms since last call)', event, self.filepath, since_last);
if (since_last >= self.delay) {
self.onchange();
last_called = Date.now();
}
else {
logger.debug('Not firing onchange due to debounce (%d < %d)', since_last, self.delay);
}
})
.on('error', function(err) {
logger.error('Error in fs.watch: %s', err);
});
};

/** FileWatcher#stop()
Close the underlying watcher.
*/
FileWatcher.prototype.stop = function() {
this.fs_watcher.close();
};

module.exports = FileWatcher;
19 changes: 0 additions & 19 deletions LICENSE

This file was deleted.

71 changes: 44 additions & 27 deletions README.md
@@ -1,46 +1,62 @@
# fs-change

Node.js process to monitor changes to specified files or directories, and execute
some specified action in response.
Monitor files or directories and execute actions in response to changes.

`fs-change` accepts two command line arguments:
File watchers and triggered actions are specified in a single file.

* `--config` where to read which files to watch.
- By default, fs-change reads settings from `~/.fs-change`, i.e., from the user's home directory.
- But the location of this file can be specified using this command line flag, e.g., `--config /opt/local/watching`.
* `--log` where to write the log file.
- Defaults to `~/Library/Logs/fs-change.log` (which can easily be viewed with Console.app)

## Installation

For Mac OS X:

# cd into this directory
touch ~/.fs-change
./index.js install
## Watch file format

This will add the `FSChange.app` application to the list of applications in your "login items,"
so that it gets started automatically.

After running `./index.js install`, either restart your computer or double click `FSChange.app`.

## `~/.fs-change` format
The location of this file defaults to `~/.fs-change`.

Each line has a glob (or simple file) on the left of a colon, and a command on
the right.

The command on the right will have the following keywords available:

- {file}, the fullpath of the matching file (usually just the string to the left
of the colon).
- {basename}, the shortname of {file}, without path or extension.
- {dirname}, the directory containing {file}.
- `{file}`, the fullpath of the matching file (usually just the string to the
left of the colon).
- `{basename}`, the shortname of {file}, without path or extension.
- `{dirname}`, the directory containing {file}.

## `~/.fs-change` example
Example:

/Users/chbrown/work/mailmaster/static/css/site.less: cd {dirname} && lessc -C site.less site.css
/Volumes/sshfs_drive/app4/static/*.less: cd {dirname} && lessc -C {basename}.less {basename}.css


## Configuration

The following environment variables can be used to configure the behavior of
the file listener.

* `OSX` [default: unset]

If set, fs-change will log to the NotificationCenter as well as STDOUT/STDERR.

* `CONFIG` [default: '~/.fs-change']

Specify the path to the configuration file specifying the files to watch.

* `DEBUG` [default: unset]

If set, fs-change will set the log level to DEBUG.


## Installation

fs-change print-launch-agent > ~/Library/LaunchAgents/npmjs.fs-change.plist
launchctl load ~/Library/LaunchAgents/npmjs.fs-change.plist

The generated LaunchAgent will log to `~/Library/Logs/fs-change.log`, which
you can view in `Console.app`.

To uninstall:

launchctl unload ~/Library/LaunchAgents/npmjs.fs-change.plist
rm ~/Library/LaunchAgents/npmjs.fs-change.plist


## TODO

* If some file does not exist, the script will continue to try the other files,
Expand All @@ -50,6 +66,7 @@ The command on the right will have the following keywords available:
should retry the filepath in question.
* Add documentation for macro syntax (`& /regex/flags/ => replacement`)


## License

Copyright © 2012–2013 Christopher Brown. [MIT Licensed](LICENSE).
Copyright 2012-2015 Christopher Brown. [MIT Licensed](http://opensource.org/licenses/MIT).
15 changes: 15 additions & 0 deletions bin/fs-change
@@ -0,0 +1,15 @@
#!/usr/bin/env node
/*jslint node: true */
var command = process.argv[2];

if (command == 'print-launch-agent') {
require('../commands/print-launch-agent');
}
else if (command == 'service') {
require('../commands/service');
}
else {
console.error('Unrecognized command: %s', command);
console.error(' Available commands: print-launch-agent, service');
process.exit(1);
}
41 changes: 0 additions & 41 deletions bin/fs-change.js

This file was deleted.

12 changes: 12 additions & 0 deletions commands/print-launch-agent.js
@@ -0,0 +1,12 @@
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');

var filepath = path.join(__dirname, '..', 'launch-agent.plist');
var template = fs.readFileSync(filepath, {encoding: 'utf8'});

var string = template.replace(/\$\{(\w+)\}/g, function(match, group, index) {
return process.env[group];
});

process.stdout.write(string);
39 changes: 39 additions & 0 deletions commands/service.js
@@ -0,0 +1,39 @@
#!/usr/bin/env node
var path = require('path');
var child_process = require('child_process');
var _ = require('underscore');
var logger = require('winston');

var config = require('../config');
var FileWatcher = require('../FileWatcher');

if (process.env.OSX) {
var NotificationCenterTransport = require('winston-notification-center');
logger.add(NotificationCenterTransport, {title: 'File system watcher'});
}

logger.level = process.env.DEBUG ? 'debug' : 'info';

var config_filepath = (process.env.CONFIG || '~/.fs-change').replace(/^~/, process.env.HOME);

var file_watchers = [];
var restart = function() {
logger.debug('Stopping %d FileWatchers', file_watchers.length);
_.invoke(file_watchers, 'stop');
file_watchers.length = 0;
config.loadFileWatchers(config_filepath, function(err, new_file_watchers) {
if (err) {
logger.error('Error reading config file: %s', err);
process.exit(1);
}

Array.prototype.push.apply(file_watchers, new_file_watchers);

logger.debug('Starting %d FileWatchers', file_watchers.length);
_.invoke(file_watchers, 'start');
});
};

var config_watcher = new FileWatcher(config_filepath, 2000, restart);
config_watcher.start();
restart();

0 comments on commit 6dedf1b

Please sign in to comment.