const std = @import("std"); const Request = @import("request.zig").Request; const SessionGetFields = @import("request.zig").SessionGetFields; pub const ClientOptions = extern struct { host: [*:0]const u8, port: u16, https: bool, user: ?[*:0]const u8 = null, password: ?[*:0]const u8 = null, }; pub const Client = struct { uri: std.Uri, allocator: std.mem.Allocator, http_client: std.http.Client, http_headers: std.http.Headers, current_session_id: []u8 = "", pub fn init(allocator: std.mem.Allocator, opts: ClientOptions) Client { const base_url = std.Uri{ .path = "/transmission/rpc", .scheme = blk: { if (opts.https) { break :blk "https"; } else { break :blk "http"; } }, .host = std.mem.span(opts.host), .port = opts.port, .user = null, .password = null, .query = null, .fragment = null, }; return Client{ .uri = base_url, .allocator = allocator, .http_client = std.http.Client{ .allocator = allocator }, .http_headers = std.http.Headers{ .allocator = allocator }, }; } pub fn deinit(self: *Client) void { self.http_headers.deinit(); self.http_client.deinit(); self.allocator.free(self.current_session_id); } fn setSessionId(self: *Client, r: std.http.Client.Response) !void { self.allocator.free(self.current_session_id); const session_id = r.headers.getFirstValue("X-Transmission-Session-Id") orelse return error.NoSessionId; self.current_session_id = try std.fmt.allocPrint(self.allocator, "{s}", .{session_id}); } fn newRequest(self: *Client) !std.http.Client.Request { try self.http_headers.append("X-Transmission-Session-Id", self.current_session_id); return try self.http_client.request(.POST, self.uri, self.http_headers, .{}); } pub fn do(self: *Client, req: Request) ![]u8 { var real_req = try self.newRequest(); defer real_req.deinit(); var payload = std.ArrayList(u8).init(self.allocator); defer payload.deinit(); try std.json.stringify(req, .{}, payload.writer()); real_req.transfer_encoding = std.http.Client.RequestTransfer{ .content_length = payload.items.len, }; try real_req.start(); if (try real_req.write(payload.items) != payload.items.len) { return error.InvalidSize; } try real_req.finish(); try real_req.wait(); if (real_req.response.status == std.http.Status.conflict) { try self.setSessionId(real_req.response); return self.do(req); } const body = try real_req.reader().readAllAlloc(self.allocator, 9000); return body; } }; pub fn sessionGet(client: *Client) ![]u8 { const r = Request{ .method = .session_get, .arguments = .{ .session_get = .{ .fields = &[_]SessionGetFields{ .version, .utp_enabled }, }, }, }; const body = try client.do(r); return body; }