diff --git a/README.md b/README.md index 1a55395..4b93ee0 100644 --- a/README.md +++ b/README.md @@ -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: - `folder`: Syncthing folder ID to watch - `path_pattern`: Regular expression to match file paths + - `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: - `${path}`: Full path to the changed file - `${folder}`: Folder ID where the change occurred @@ -81,4 +82,4 @@ zig build test ## License -MIT License \ No newline at end of file +MIT License diff --git a/src/main.zig b/src/main.zig index bcc7752..5ce35d9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6,6 +6,11 @@ const EventPoller = lib.EventPoller; const Args = struct { config_path: []const u8 = "config.json", syncthing_url: ?[]const u8 = null, + + pub fn deinit(self: Args, allocator: std.mem.Allocator) void { + allocator.free(self.config_path); + if (self.syncthing_url) |url| allocator.free(url); + } }; pub const std_options: std.Options = .{ @@ -32,6 +37,7 @@ pub fn main() !u8 { const allocator = gpa.allocator(); const args = try parseArgs(allocator); + defer args.deinit(allocator); const file = try std.fs.cwd().openFile(args.config_path, .{}); defer file.close(); @@ -61,7 +67,6 @@ pub fn main() !u8 { } const stdout = std.io.getStdOut().writer(); - try stdout.print("Monitoring Syncthing events at {s}\n", .{config.syncthing_url}); var last_id: ?i64 = null; const connection_pool = std.http.Client.ConnectionPool{}; @@ -76,6 +81,8 @@ pub fn main() !u8 { config, connection_pool, ); + if (last_id == null) // first run + try stdout.print("Monitoring Syncthing events at {s}\n", .{try poller.url()}); defer last_id = poller.last_id; poller.last_id = last_id; const events = poller.poll() catch |err| switch (err) { diff --git a/src/root.zig b/src/root.zig index de4128b..a0f481e 100644 --- a/src/root.zig +++ b/src/root.zig @@ -13,6 +13,7 @@ pub const Watcher = struct { folder: []const u8, path_pattern: []const u8, action: []const u8, + event_type: []const u8 = "ItemFinished", command: []const u8, compiled_pattern: ?mvzr.Regex = null, @@ -43,6 +44,7 @@ pub const Watcher = struct { pub const SyncthingEvent = struct { id: i64, + event_type: []const u8, data_type: []const u8, folder: []const u8, path: []const u8, @@ -54,6 +56,7 @@ pub const SyncthingEvent = struct { return SyncthingEvent{ .id = value.object.get("id").?.integer, .time = try allocator.dupe(u8, value.object.get("time").?.string), + .event_type = try allocator.dupe(u8, value.object.get("type").?.string), .data_type = try allocator.dupe(u8, data.get("type").?.string), .folder = try allocator.dupe(u8, data.get("folder").?.string), .action = try allocator.dupe(u8, data.get("action").?.string), @@ -87,6 +90,28 @@ pub const EventPoller = struct { }; } + pub fn url(self: EventPoller) ![]const u8 { + const watched_events = blk: { + var type_set = std.StringArrayHashMap(void).init(self.allocator); + try type_set.ensureTotalCapacity(self.config.watchers.len); + for (self.config.watchers) |watcher| + type_set.putAssumeCapacity(watcher.event_type, {}); + break :blk try std.mem.join(self.allocator, ",", type_set.keys()); + }; + var since_buf: [100]u8 = undefined; + const since = if (self.last_id) |id| + try std.fmt.bufPrint(&since_buf, "&since={d}", .{id}) + else + ""; + return try std.fmt.allocPrint( + self.allocator, + "{s}/rest/events?events={s}{s}", + .{ + self.config.syncthing_url, watched_events, since, + }, + ); + } + pub fn poll(self: *EventPoller) ![]SyncthingEvent { var client = std.http.Client{ .allocator = self.allocator, .connection_pool = self.connection_pool }; var arena = std.heap.ArenaAllocator.init(self.allocator); @@ -101,22 +126,13 @@ pub const EventPoller = struct { const MAX_UCF_RETRIES: usize = 20; var retry_count: usize = 0; const first_run = self.last_id == null; + const poll_url = try self.url(); while (retry_count < self.config.max_retries) : (retry_count += 1) { - var url_buf: [1024]u8 = undefined; - var since_buf: [100]u8 = undefined; - const since = if (self.last_id) |id| - try std.fmt.bufPrint(&since_buf, "&since={d}", .{id}) - else - ""; - const url = try std.fmt.bufPrint(&url_buf, "{s}/rest/events?events=ItemFinished{s}", .{ - self.config.syncthing_url, since, - }); - var al = std.ArrayList(u8).init(self.allocator); defer al.deinit(); const response = client.fetch(.{ - .location = .{ .url = url }, + .location = .{ .url = poll_url }, .response_storage = .{ .dynamic = &al }, .headers = .{ .authorization = .{ .override = auth },