From c7412022c3c607c809f774db2459f9259ba95038 Mon Sep 17 00:00:00 2001 From: Christian Segundo Date: Sat, 17 Jun 2023 02:54:34 +0200 Subject: one step closer --- src/main.zig | 61 ++++++++++------- src/request.zig | 14 ++-- src/response.zig | 47 +++++++++++++ src/transmission.zig | 68 +++++++++++------- src/types.zig | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 324 insertions(+), 55 deletions(-) create mode 100644 src/response.zig create mode 100644 src/types.zig (limited to 'src') diff --git a/src/main.zig b/src/main.zig index a52079a..b01188b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -14,32 +14,43 @@ export fn add(a: i32, b: i32) i32 { var client = transmission.Client.init(allocator, clientOptions); defer client.deinit(); - { - const body = transmission.session_get_raw(&client, null) catch |err| { - std.debug.print("error: {any}\n", .{err}); - unreachable; - }; - defer allocator.free(body); - std.debug.print("body: {s}\n", .{body}); - } + //{ + //const body = transmission.session_get_raw(&client, null) catch |err| { + //std.debug.print("error: {any}\n", .{err}); + //unreachable; + //}; + //defer allocator.free(body); + //std.debug.print("body: {s}\n", .{body}); + //} { - const body = transmission.torrent_get_raw(&client, null) catch |err| { + const body = transmission.torrent_get_(&client, null) catch |err| { std.debug.print("error: {any}\n", .{err}); unreachable; }; - defer allocator.free(body); - std.debug.print("body: {s}\n", .{body}); + //defer allocator.free(body); + for (body.arguments.torrent_get.torrents.?) |t| { + std.debug.print("name: {any}\n", .{t}); + } } - { - const body = transmission.session_set_raw(&client, .{ .peer_port = 51413 }) catch |err| { - std.debug.print("error: {any}\n", .{err}); - unreachable; - }; - defer allocator.free(body); - std.debug.print("body: {s}\n", .{body}); - } + //{ + //const body = transmission.torrent_get_(&client, null) catch |err| { + //std.debug.print("error: {any}\n", .{err}); + //unreachable; + //}; + ////defer allocator.free(body); + //std.debug.print("body: {any}\n", .{body}); + //} + + //{ + //const body = transmission.session_set_raw(&client, .{ .peer_port = 51413 }) catch |err| { + //std.debug.print("error: {any}\n", .{err}); + //unreachable; + //}; + //defer allocator.free(body); + //std.debug.print("body: {s}\n", .{body}); + //} return a + b; } @@ -76,7 +87,7 @@ export fn c_session_get(client: ?*anyopaque, buf: [*]u8) c_int { return 0; } -export fn c_torrent_get(client: ?*anyopaque) c_int { +export fn c_torrent_get(client: ?*anyopaque) [*:0]u8 { var real_client: *transmission.Client = @ptrCast(*transmission.Client, @alignCast( @alignOf(transmission.Client), client.?, @@ -88,8 +99,12 @@ export fn c_torrent_get(client: ?*anyopaque) c_int { }; defer allocator.free(body); - std.debug.print("body: {s}\n", .{body}); - return 0; + // TODO: use the same pointer of body but gotta go fast :( + var foo = std.ArrayList(u8).init(allocator); + foo.insertSlice(0, body) catch unreachable; + + //std.debug.print("body: {s}\n", .{body}); + return foo.toOwnedSliceSentinel(0) catch unreachable; } test "c api" { @@ -100,7 +115,7 @@ test "c api" { }; var foo = c_client_init(clientOptions); _ = c_session_get(foo, undefined); - _ = c_torrent_get(foo, undefined); + _ = c_torrent_get(foo); c_client_deinit(foo); } diff --git a/src/request.zig b/src/request.zig index 478999b..214f997 100644 --- a/src/request.zig +++ b/src/request.zig @@ -1,7 +1,9 @@ const std = @import("std"); const util = @import("util.zig"); -const Method = enum { +pub const Request = @This(); + +pub const Method = enum { // Torrent requests: Torrent action // https://github.com/transmission/transmission/blob/main/docs/rpc-spec.md#31-torrent-action-requests torrent_start, @@ -53,8 +55,8 @@ const Method = enum { }; pub const TorrentIDs = union(enum) { - single: usize, - many: []const usize, + single: i32, + many: []const i32, recently_active, pub fn jsonStringify(self: TorrentIDs, options: std.json.StringifyOptions, out_stream: anytype) !void { @@ -309,7 +311,7 @@ pub const SessionSet = struct { } }; -pub const Request = struct { +pub const Object = struct { method: Method, arguments: union(Method) { torrent_start: TorrentStart, @@ -358,7 +360,7 @@ test "json request encoding" { const test_case = struct { name: []const u8, expected: []const u8, - request: Request, + request: Object, fn run(self: @This()) !void { var req = std.ArrayList(u8).init(std.testing.allocator); @@ -429,7 +431,7 @@ test "json request encoding" { .method = .torrent_reannounce, .arguments = .{ .torrent_reannounce = .{ - .ids = .{ .many = &[_]usize{ 1, 2 } }, + .ids = .{ .many = &[_]i32{ 1, 2 } }, }, }, }, diff --git a/src/response.zig b/src/response.zig new file mode 100644 index 0000000..8964de4 --- /dev/null +++ b/src/response.zig @@ -0,0 +1,47 @@ +const std = @import("std"); +const Request = @import("request.zig"); +const Types = @import("types.zig"); + +pub const Response = @This(); + +pub const TorrentGet = struct { + pub const Arguments = struct { + torrents: ?[]Types.Torrent = null, + }; + + result: []const u8, + arguments: Arguments, +}; + +pub const Object = struct { + pub const Arguments = union(Request.Method) { + torrent_start, + torrent_start_now, + torrent_stop, + torrent_verify, + torrent_reannounce, + torrent_set, + torrent_get: TorrentGet.Arguments, + torrent_add, + torrent_remove, + torrent_set_location, + torrent_rename_path, + session_get, + session_set, + }; + + result: []const u8, + arguments: Arguments, + + pub fn init(arguments: Arguments, result: []const u8) Object { + switch (arguments) { + .torrent_get => { + return Object{ + .result = result, + .arguments = arguments, + }; + }, + else => unreachable, + } + } +}; diff --git a/src/transmission.zig b/src/transmission.zig index dd058bb..409b4b1 100644 --- a/src/transmission.zig +++ b/src/transmission.zig @@ -1,10 +1,10 @@ const std = @import("std"); +const http = std.http; + const util = @import("util.zig"); -const Request = @import("request.zig").Request; -const SessionGet = @import("request.zig").SessionGet; -const SessionSet = @import("request.zig").SessionSet; -const TorrentGet = @import("request.zig").TorrentGet; +const Request = @import("request.zig"); +const Response = @import("response.zig"); pub const ClientOptions = extern struct { host: [*:0]const u8, @@ -17,8 +17,8 @@ pub const ClientOptions = extern struct { pub const Client = struct { uri: std.Uri, allocator: std.mem.Allocator, - http_client: std.http.Client, - http_headers: std.http.Headers, + http_client: http.Client, + http_headers: http.Headers, current_session_id: []u8 = "", pub fn init(allocator: std.mem.Allocator, opts: ClientOptions) Client { @@ -42,8 +42,8 @@ pub const Client = struct { return Client{ .uri = base_url, .allocator = allocator, - .http_client = std.http.Client{ .allocator = allocator }, - .http_headers = std.http.Headers{ .allocator = allocator }, + .http_client = http.Client{ .allocator = allocator }, + .http_headers = http.Headers{ .allocator = allocator }, }; } @@ -53,18 +53,18 @@ pub const Client = struct { self.allocator.free(self.current_session_id); } - fn setSessionId(self: *Client, r: std.http.Client.Response) !void { + fn setSessionId(self: *Client, r: 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 { + fn newRequest(self: *Client) !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 { + pub fn do(self: *Client, req: Request.Object) ![]u8 { var real_req = try self.newRequest(); defer real_req.deinit(); @@ -73,7 +73,7 @@ pub const Client = struct { try std.json.stringify(req, .{}, payload.writer()); - real_req.transfer_encoding = std.http.Client.RequestTransfer{ + real_req.transfer_encoding = http.Client.RequestTransfer{ .content_length = payload.items.len, }; @@ -82,19 +82,17 @@ pub const Client = struct { return error.InvalidSize; } - std.debug.print("request: {s}\n", .{payload.items}); - try real_req.finish(); try real_req.wait(); - if (real_req.response.status == std.http.Status.conflict) { + if (real_req.response.status == 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 + // TODO: making max_append_size this large // all the time doesn't feel right try real_req.reader().readAllArrayList(&body, 9000000000); @@ -102,12 +100,12 @@ pub const Client = struct { } }; -pub fn session_get_raw(client: *Client, session_get: ?SessionGet) ![]u8 { - const default: SessionGet = .{ - .fields = SessionGet.all_fields, +pub fn session_get_raw(client: *Client, session_get: ?Request.SessionGet) ![]u8 { + const default: Request.SessionGet = .{ + .fields = Request.SessionGet.all_fields, }; - const r = Request{ + const r = Request.Object{ .method = .session_get, .arguments = .{ .session_get = session_get orelse default }, }; @@ -115,12 +113,15 @@ pub fn session_get_raw(client: *Client, session_get: ?SessionGet) ![]u8 { return body; } -pub fn torrent_get_raw(client: *Client, torrent_get: ?TorrentGet) ![]u8 { - const default: TorrentGet = .{ - .fields = TorrentGet.all_fields, +pub fn torrent_get_raw(client: *Client, torrent_get: ?Request.TorrentGet) ![]u8 { + const default: Request.TorrentGet = .{ + .fields = Request.TorrentGet.all_fields, }; + //const default: Request.TorrentGet = .{ + //.fields = &[_]Request.TorrentGet.Fields{.name}, + //}; - const r = Request{ + const r = Request.Object{ .method = .torrent_get, .arguments = .{ .torrent_get = torrent_get orelse default }, }; @@ -128,8 +129,23 @@ pub fn torrent_get_raw(client: *Client, torrent_get: ?TorrentGet) ![]u8 { return body; } -pub fn session_set_raw(client: *Client, session_set: SessionSet) ![]u8 { - const r = Request{ +pub fn torrent_get_(client: *Client, torrent_get: ?Request.TorrentGet) !Response.Object { + @setEvalBranchQuota(100000); + const body = try torrent_get_raw(client, torrent_get); + const decoded: Response.TorrentGet = try std.json.parseFromSlice( + Response.TorrentGet, + client.allocator, + body, + std.json.ParseOptions{ + .ignore_unknown_fields = true, + .duplicate_field_behavior = .@"error", + }, + ); + return Response.Object.init(.{ .torrent_get = decoded.arguments }, decoded.result); +} + +pub fn session_set_raw(client: *Client, session_set: Request.SessionSet) ![]u8 { + const r = Request.Object{ .method = .session_set, .arguments = .{ .session_set = session_set }, }; diff --git a/src/types.zig b/src/types.zig new file mode 100644 index 0000000..b8029ca --- /dev/null +++ b/src/types.zig @@ -0,0 +1,189 @@ +// types are from: +// https://github.com/transmission/transmission/blob/76166d8fa71f351fe46221d737f8849b23f551f7/libtransmission/transmission.h#L1604 +// where: +// time_t => i64 +// float => f32 +// uint16_t => u16 +pub const Torrent = struct { + // The last time we uploaded or downloaded piece data on this torrent. + activityDate: ?i64 = null, + + // When the torrent was first added. + addedDate: ?i64 = null, + + //availability: []struct {}, array (see below) tr_torrepieceCountntAvailability() + + //bandwidthPriority: ?usize = null, + comment: ?[]u8 = null, + + // Byte count of all the corrupt data you've ever downloaded for + // this torrent. If you're on a poisoned torrent, this number can + // grow very large. + corruptEver: ?u64 = null, + + creator: ?[]u8 = null, + //dateCreated: ?usize = null, + + // Byte count of all the piece data we want and don't have yet, + // but that a connected peer does have. + desiredAvailable: ?u64 = null, + + // When the torrent finished downloading. + doneDate: ?i64 = null, + + downloadDir: ?[]u8 = null, + + // Byte count of all the non-corrupt data you've ever downloaded + // for this torrent. If you deleted the files and downloaded a second + // time, this will be `2*totalSize`. + downloadedEver: ?u64 = null, + + //downloadLimit: ?usize = null, + downloadLimited: ?bool = null, + + // The last time during this session that a rarely-changing field + // changed -- e.g. fields like trackers, filenames, name + // or download directory. RPC clients can monitor this to know when + // to reload fields that rarely change. + editDate: ?i64 = null, + + //@"error": ?usize = null, + errorString: ?[]u8 = null, + + // If downloading, estimated number of seconds left until the torrent is done. + // If seeding, estimated number of seconds left until seed ratio is reached. + eta: ?i64 = null, + + // If seeding, number of seconds left until the idle time limit is reached. + etaIdle: ?i64 = null, + + //@"file-count": ?usize = null, + ////files array (see below) n/a + ////fileStats array (see below) n/a + group: ?[]u8 = null, + hashString: ?[]u8 = null, + + // Byte count of all the partial piece data we have for this torrent. + // As pieces become complete, this value may decrease as portions of it + // are moved to `corrupt` or `haveValid`. + haveUnchecked: ?u64 = null, + + // Byte count of all the checksum-verified data we have for this torrent. + haveValid: ?u64 = null, + + honorsSessionLimits: ?bool = null, + + id: ?i32 = null, + + isFinished: ?bool = null, + isPrivate: ?bool = null, + isStalled: ?bool = null, + labels: ?[][]u8 = null, + + // Byte count of how much data is left to be downloaded until we've got + // all the pieces that we want. + leftUntilDone: ?u64 = null, + + magnetLink: ?[]u8 = null, + //manualAnnounceTime: ?usize = null, + //maxConnectedPeers: ?usize = null, + + // How much of the metadata the torrent has. + // For torrents added from a torrent this will always be 1. + // For magnet links, this number will from from 0 to 1 as the metadata is downloaded. + // Range is [0..1] + metadataPercentComplete: ?f32 = null, + + name: ?[]u8 = null, + //@"peer-limit": ?usize = null, + ////peers array (see below) n/a + + // Number of peers that we're connected to + peersConnected: ?u16 = null, + + ////peersFrom object (see below) n/a + + // Number of peers that we're sending data to. + peersGettingFromUs: ?u16 = null, + + // Number of peers that are sending data to us. + peersSendingToUs: ?u16 = null, + + // How much has been downloaded of the entire torrent. + // Range is [0..1] + percentComplete: ?f32 = null, + + // How much has been downloaded of the files the user wants. This differs + // from percentComplete if the user wants only some of the torrent's files. + // Range is [0..1] + // See `leftUntilDone` + percentDone: ?f32 = null, + + pieces: ?[]u8 = null, + //pieceCount: ?usize = null, + //pieceSize: ?usize = null, + ////priorities array (see below) n/a + @"primary-mime-type": ?[]u8 = null, + + // This torrent's queue position. + // All torrents have a queue position, even if it's not queued. + queuePosition: ?usize = null, + + //@"rateDownload (B/s)": ?usize = null, + //@"rateUpload (B/s)": ?usize = null, + + // When `tr_stat.activity` is `TR_STATUS_CHECK` or `TR_STATUS_CHECK_WAIT`, + // this is the percentage of how much of the files has been + // verified. When it gets to 1, the verify process is done. + // Range is [0..1] + // @see `tr_stat.activity` + recheckProgress: ?f32 = null, + + // Number of seconds since the last activity (or since started). + // -1 if activity is not seeding or downloading. + secondsDownloading: ?f32 = null, + + // Cumulative seconds the torrent's ever spent seeding. + secondsSeeding: ?f32 = null, + + //seedIdleLimit: ?usize = null, + //seedIdleMode: ?usize = null, + + // ?? + seedRatioLimit: ?f32 = null, + + //seedRatioMode: ?usize = null, + sequentialDownload: ?bool = null, + + // Byte count of all the piece data we'll have downloaded when we're done, + // whether or not we have it yet. This may be less than `totalSize` + // if only some of the torrent's files are wanted. + sizeWhenDone: ?u64 = null, + + // When the torrent was last started. + startDate: ?i64 = null, + + //status: ?usize = null, + ////trackers array (see below) n/a + trackerList: ?[]u8 = null, + ////trackerStats array (see below) n/a + //totalSize: ?usize = null, + torrentFile: ?[]u8 = null, + + // Byte count of all data you've ever uploaded for this torrent. + uploadedEver: ?u64 = null, + + //uploadLimit: ?usize = null, + uploadLimited: ?bool = null, + + // ?? + uploadRatio: ?f32 = null, + + ////wanted array (see below) n/a + + // ?? + webseeds: ?[][]u8 = null, + + // Number of webseeds that are sending data to us. + webseedsSendingToUs: ?u16 = null, +}; -- cgit v1.2.3