const std = @import("std"); const util = @import("util.zig"); const Request = @import("request.zig").Request; const SessionGetFields = @import("request.zig").SessionGetFields; const SessionGet = @import("request.zig").SessionGet; const TorrentGet = @import("request.zig").TorrentGet; const TorrentGetFields = @import("request.zig").TorrentGetFields; 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); } var body = std.ArrayList(u8).init(self.allocator); // TODO making max_append_size this large // all the time doesn't feel right try real_req.reader().readAllArrayList(&body, 9000000000); return body.toOwnedSlice(); } }; pub fn session_get_raw(client: *Client, session_get: ?SessionGet) ![]u8 { const default: SessionGet = .{ .fields = comptime util.enumFieldsToSlice(SessionGetFields), }; const r = Request{ .method = .session_get, .arguments = .{ .session_get = session_get orelse default }, }; const body = try client.do(r); return body; } pub fn torrent_get_raw(client: *Client, torrent_get: ?TorrentGet) ![]u8 { const default: TorrentGet = .{ .fields = comptime util.enumFieldsToSlice(TorrentGetFields), }; const r = Request{ .method = .torrent_get, .arguments = .{ .torrent_get = torrent_get orelse default }, }; const body = try client.do(r); return body; }