1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
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;
}
|