summaryrefslogtreecommitdiff
path: root/src/transmission.zig
blob: fec1b1233e5b455d96d5461b8119a9eb36f2c631 (plain) (blame)
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
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;
}