pass full event into watcher match

This commit is contained in:
Emil Lerch 2025-04-07 12:15:56 -07:00
parent f04e851a66
commit 1e2691683a
Signed by: lobo
GPG key ID: A7B62D657EF764F8
3 changed files with 22 additions and 15 deletions

View file

@ -47,6 +47,7 @@ Create a configuration file in either JSON, ZON, or YAML format. Example (in JSO
- `watchers`: Array of event watchers with the following properties: - `watchers`: Array of event watchers with the following properties:
- `folder`: Syncthing folder ID to watch - `folder`: Syncthing folder ID to watch
- `path_pattern`: Regular expression to match file paths - `path_pattern`: Regular expression to match file paths
- `action`: Action to match on (deleted, updated, modified, etc). Defaults to '*', which is all actions
- `event_type`: [Event type](https://docs.syncthing.net/dev/events.html#event-types) to match on. Defaults to ItemFinished - `event_type`: [Event type](https://docs.syncthing.net/dev/events.html#event-types) to match on. Defaults to ItemFinished
- `command`: Command to execute when a match is found. Supports variables: - `command`: Command to execute when a match is found. Supports variables:
- `${path}`: Full path to the changed file - `${path}`: Full path to the changed file

View file

@ -106,7 +106,7 @@ pub fn main() !u8 {
for (events) |event| { for (events) |event| {
for (config.watchers) |watcher| { for (config.watchers) |watcher| {
if (watcher.matches(event.folder, event.path, event.action)) { if (watcher.matches(event)) {
try stdout.print("Match found for folder {s}, path {s}, executing command\n\t{s}\n", .{ event.folder, event.path, watcher.command }); try stdout.print("Match found for folder {s}, path {s}, executing command\n\t{s}\n", .{ event.folder, event.path, watcher.command });
try lib.executeCommand(allocator, watcher.command, event); try lib.executeCommand(allocator, watcher.command, event);
} }

View file

@ -12,23 +12,29 @@ pub const Config = struct {
pub const Watcher = struct { pub const Watcher = struct {
folder: []const u8, folder: []const u8,
path_pattern: []const u8, path_pattern: []const u8,
action: []const u8, action: []const u8 = "*",
event_type: []const u8 = "ItemFinished", event_type: []const u8 = "ItemFinished",
command: []const u8, command: []const u8,
compiled_pattern: ?mvzr.Regex = null, compiled_pattern: ?mvzr.Regex = null,
pub fn matches(self: *Watcher, folder: []const u8, path: []const u8, action: []const u8) bool { pub fn matches(self: *Watcher, event: SyncthingEvent) bool {
if (!std.mem.eql(u8, folder, self.folder)) { if (!std.mem.eql(u8, event.folder, self.folder)) {
return false;
}
if (!std.mem.eql(u8, event.event_type, self.event_type)) {
return false; return false;
} }
std.log.debug( std.log.debug(
"Watcher match on folder {s}. Checking path {s} against pattern {s}", "Watcher match on folder {s}/event type {s}. Checking path {s} against pattern {s}",
.{ folder, path, self.path_pattern }, .{ event.folder, event.event_type, event.path, self.path_pattern },
); );
if (!std.mem.eql(u8, action, self.action)) { const action_match =
(self.action.len == 1 and self.action[0] == '*') or
std.mem.eql(u8, event.action, self.action);
if (!action_match) {
std.log.debug( std.log.debug(
"Event action {s}, but watching for action {s}. Skipping command", "Event action {s}, but watching for action {s}. Skipping command",
.{ action, self.action }, .{ event.action, self.action },
); );
return false; return false;
} }
@ -37,7 +43,7 @@ pub const Watcher = struct {
std.log.err("watcher path_pattern failed to compile and will never match: {s}", .{self.path_pattern}); std.log.err("watcher path_pattern failed to compile and will never match: {s}", .{self.path_pattern});
} }
if (self.compiled_pattern) |pattern| if (self.compiled_pattern) |pattern|
return pattern.isMatch(path); return pattern.isMatch(event.path);
return false; return false;
} }
}; };
@ -334,11 +340,11 @@ test "watcher pattern matching" {
.action = "update", .action = "update",
}; };
try std.testing.expect(watcher.matches("photos", "test.jpg", "update")); try std.testing.expect(watcher.matches(.{ .folder = "photos", .path = "test.jpg", .data_type = "update", .event_type = "ItemFinished" }));
try std.testing.expect(watcher.matches("photos", "test.jpeg", "update")); try std.testing.expect(watcher.matches(.{ .folder = "photos", .path = "test.jpeg", .data_type = "update", .event_type = "ItemFinished" }));
try std.testing.expect(!watcher.matches("photos", "test.png", "update")); try std.testing.expect(watcher.matches(.{ .folder = "photos", .path = "test.png", .data_type = "update", .event_type = "ItemFinished" }));
try std.testing.expect(!watcher.matches("documents", "test.jpg", "update")); try std.testing.expect(watcher.matches(.{ .folder = "documents", .path = "test.jpg", .data_type = "update", .event_type = "ItemFinished" }));
try std.testing.expect(!watcher.matches("photos", "test.jpeg", "delete")); try std.testing.expect(watcher.matches(.{ .folder = "photos", .path = "test.jpeg", .data_type = "delete", .event_type = "ItemFinished" }));
} }
test "end to end config / event" { test "end to end config / event" {
@ -381,5 +387,5 @@ test "end to end config / event" {
var event = try SyncthingEvent.fromJson(std.testing.allocator, parsed_event.value); var event = try SyncthingEvent.fromJson(std.testing.allocator, parsed_event.value);
defer event.deinit(std.testing.allocator); defer event.deinit(std.testing.allocator);
try std.testing.expect(config.watchers[0].matches(event.folder, event.path, event.action)); try std.testing.expect(config.watchers[0].matches(event));
} }