First working version

master
Dustin 2015-02-01 18:57:21 -06:00
parent d8bf3d64cb
commit 439a53940c
4 changed files with 305 additions and 2 deletions

View File

@ -1,6 +1,8 @@
bin_PROGRAMS = onchanged
onchanged_VALASOURCES = \
src/watchedpath.vala \
src/watchlist.vala \
src/onchanged.vala
onchanged_SOURCES = $(onchanged_VALASOURCES)

View File

@ -1,3 +1,62 @@
public static int main() {
namespace onchanged {
public bool debug = false;
public class Main : Object {
[CCode(array_length = false, array_null_terminated = true)]
private static string[]? action_files;
private const OptionEntry[] options = {
{"debug", 'D', 0, OptionArg.NONE, ref debug,
"Write debug information to stdout", null},
{"", 0, 0, OptionArg.FILENAME_ARRAY, ref action_files,
"Action definition files", "[FILENAME [...]]"},
{ null },
};
private static void parse_args(string[] args) throws OptionError {
action_files = {
Path.build_filename(
Environment.get_user_config_dir(),
"onchanged"
)
};
var ctx = new OptionContext();
ctx.set_summary("Perform actions when files change");
ctx.set_help_enabled(true);
ctx.add_main_entries(options, null);
ctx.parse(ref args);
}
public static int main(string[] args) {
try {
parse_args(args);
} catch (OptionError e) {
stderr.printf("error: %s\n", e.message);
return 2;
}
var watches = new WatchList();
foreach (string filename in action_files) {
var f = File.new_for_commandline_arg(filename);
switch(f.query_file_type(FileQueryInfoFlags.NONE)) {
case FileType.UNKNOWN:
stderr.printf("Skipping missing file %s\n", f.get_path());
continue;
case FileType.DIRECTORY:
watches.read_config_dir(f);
break;
default:
watches.read_config(f.get_path());
break;
}
}
var loop = new MainLoop();
loop.run();
return 0;
}
}
}

144
src/watchedpath.vala Normal file
View File

@ -0,0 +1,144 @@
namespace onchanged {
public class WatchedPath : Object {
public string path { get; construct; }
public string[] events { get; construct; }
public bool recursive { get; construct; }
private string _action;
private HashTable<string, FileMonitor> _monitors;
construct {
_monitors =
new HashTable<string, FileMonitor>(str_hash, str_equal);
var file = File.new_for_path(this.path);
watch(file);
var ftype = file.query_file_type(FileQueryInfoFlags.NONE);
if (ftype == FileType.DIRECTORY && recursive) {
watch_children(file);
}
}
public WatchedPath(string path, string[] events,
bool recursive = true) {
Object(
path: path,
events: events,
recursive: recursive
);
}
private void unwatch(File file) {
var path = file.get_path();
if (_monitors.get(path) != null) {
if (onchanged.debug) {
stdout.printf("No longer watching path %s\n", path);
}
_monitors.remove(path);
}
}
private void watch(File file) {
var path = file.get_path();
if (_monitors.get(path) == null) {
if (onchanged.debug) {
stdout.printf("Watching path %s\n", path);
}
FileMonitor mon;
try {
mon = file.monitor(FileMonitorFlags.NONE);
} catch (GLib.Error e) {
stderr.printf(
"Failed to monitor %s: %s\n", path, e.message
);
return;
}
mon.changed.connect(handle_event);
_monitors.insert(file.get_path(), mon);
}
}
private void watch_children(File dir) {
try {
var enumerator = dir.enumerate_children(
FileAttribute.STANDARD_NAME,
FileQueryInfoFlags.NONE
);
FileInfo info;
while ((info = enumerator.next_file()) != null) {
var f = dir.resolve_relative_path(info.get_name());
if (info.get_file_type() == FileType.DIRECTORY) {
watch(f);
watch_children(f);
}
}
} catch (GLib.Error e) {
stderr.printf("%s\n", e.message);
}
}
private void handle_event(File file, File? other_file,
FileMonitorEvent event_type) {
if (onchanged.debug) {
stdout.printf("%s: ", event_type.to_string());
if (other_file != null) {
stdout.printf(
"%s, %s\n", file.get_path(), other_file.get_path()
);
} else {
stdout.printf("%s\n", file.get_path());
}
}
var file_type = file.query_file_type(FileQueryInfoFlags.NONE);
switch (event_type) {
case FileMonitorEvent.CREATED:
if (file_type == FileType.DIRECTORY && recursive) {
watch(file);
watch_children(file);
}
do_action(file, "created");
break;
case FileMonitorEvent.CHANGES_DONE_HINT:
do_action(file, "changed");
break;
case FileMonitorEvent.DELETED:
unwatch(file);
do_action(file, "deleted");
break;
}
}
private void do_action(File file, string event) {
if (!(event in events)) {
return;
}
string escaped_path = "'%s'".printf(
file.get_path().replace("'", "'\\''")
);
string command = _action
.replace("$#", escaped_path)
.replace("$%", event);
if (onchanged.debug) {
stdout.printf("%s\n", command);
}
try {
string[] argv;
Shell.parse_argv(command, out argv);
Process.spawn_async("/", argv, null, SpawnFlags.SEARCH_PATH,
null, null);
} catch (GLib.ShellError e) {
stderr.printf("Error parsing action: %s\n", e.message);
} catch (GLib.SpawnError e) {
stderr.printf("Error running action: %s\n", e.message);
}
}
public void set_action(string action) {
_action = action;
}
}
}

98
src/watchlist.vala Normal file
View File

@ -0,0 +1,98 @@
namespace onchanged {
public class WatchList : Object {
private List<WatchedPath> watches;
construct {
watches = new List<WatchedPath>();
}
public void read_config(string file) {
if (onchanged.debug) {
stdout.printf("Reading actions from %s\n", file);
stdout.flush();
}
var conf = new KeyFile();
conf.set_list_separator(',');
try {
conf.load_from_file(file, KeyFileFlags.NONE);
} catch (FileError e) {
stderr.printf("Failed to read %s: %s\n", file, e.message);
} catch (KeyFileError e) {
stderr.printf("Failed to parse %s: %s\n", file, e.message);
return;
}
foreach (string path in conf.get_groups()) {
string action;
try {
action = conf.get_string(path, "action");
} catch (KeyFileError e) {
stderr.printf(
"Error processing section %s: %s\n", path, e.message
);
continue;
}
string[]? events;
try {
events = conf.get_string_list(path, "events");
} catch (KeyFileError e) {
if (!(e is KeyFileError.KEY_NOT_FOUND)) {
stderr.printf(
"Warning processing section %s: %s\n", path,
e.message
);
}
events = {};
}
bool recursive;
try {
recursive = conf.get_boolean(path, "recursive");
} catch (KeyFileError e) {
if (!(e is KeyFileError.KEY_NOT_FOUND)) {
stderr.printf(
"Warning processing section %s: %s\n", path,
e.message
);
}
recursive = false;
}
var watch = new WatchedPath(path, events, recursive);
watch.set_action(action);
watches.append(watch);
}
}
public void read_config_dir(File dir) {
if (onchanged.debug) {
stdout.printf(
"Reading all action files in %s\n", dir.get_path()
);
}
try {
var enumerator = dir.enumerate_children(
FileAttribute.STANDARD_NAME,
FileQueryInfoFlags.NONE
);
FileInfo info;
while ((info = enumerator.next_file()) != null) {
var f = dir.resolve_relative_path(info.get_name());
if (info.get_file_type() == FileType.DIRECTORY) {
read_config_dir(f);
} else {
read_config(f.get_path());
}
}
} catch (GLib.Error e) {
stderr.printf("%s\n", e.message);
}
}
}
}