diff options
-rw-r--r-- | src/bencode.zig | 7 | ||||
-rw-r--r-- | src/metainfo.zig | 54 |
2 files changed, 54 insertions, 7 deletions
diff --git a/src/bencode.zig b/src/bencode.zig index d09f394..60a3746 100644 --- a/src/bencode.zig +++ b/src/bencode.zig @@ -93,6 +93,13 @@ pub const BValue = union(enum) { else => return error.WrongType, } } + + pub fn asList(self: BValue) !std.ArrayList(BValue) { + switch (self) { + .list => |l| return l, + else => return error.WrongType, + } + } }; pub fn bdecodeBuf(a: std.mem.Allocator, buf: []const u8) !BValue { diff --git a/src/metainfo.zig b/src/metainfo.zig index 9fa4788..a202b0e 100644 --- a/src/metainfo.zig +++ b/src/metainfo.zig @@ -2,6 +2,7 @@ const std = @import("std"); const bencode = @import("bencode.zig"); const MetaInfo = @This(); +pub const Error = (error{Malformatted} || std.mem.Allocator.Error); pub const Info = struct { pub const File = struct { @@ -12,36 +13,75 @@ pub const Info = struct { piece_length: u64, pieces: []const u8, files: []File, - pub fn parse(b: bencode.BValue) !Info { + pub fn parse(a: std.mem.Allocator, b: bencode.BValue) Error!Info { var d = b.asDict() catch return error.Malformatted; const pl = d.get("piece length") orelse return error.Malformatted; const pp = d.get("pieces") orelse return error.Malformatted; + var files = std.ArrayList(File).init(a); + defer files.deinit(); + if (d.get("files")) |f| { + // multi-file mode + const l = f.asList() catch return error.Malformatted; + for (l.items) |fi| { + const fd = fi.asDict() catch return error.Malformatted; + const fin = fd.get("name") orelse return error.Malformatted; + const fl = fd.get("length") orelse return error.Malformatted; + const fp = fd.get("path") orelse return error.Malformatted; + try files.append(.{ + .name = fin.asString() catch return error.Malformatted, + .length = fl.asInt(u64) catch return error.Malformatted, + .path = fp.asString() catch return error.Malformatted, + }); + } + } else { + // single-file mode + const fin = d.get("name") orelse return error.Malformatted; + const fl = d.get("length") orelse return error.Malformatted; + try files.append(.{ + .name = fin.asString() catch return error.Malformatted, + .length = fl.asInt(u64) catch return error.Malformatted, + .path = fin.asString() catch return error.Malformatted, // just use the file name as path + }); + } + return .{ .piece_length = pl.asInt(u64) catch return error.Malformatted, .pieces = pp.asString() catch return error.Malformatted, - .files = &[_]File{}, + .files = try files.toOwnedSlice(), }; } + + pub fn deinit(self: *Info, a: std.mem.Allocator) void { + a.free(self.files); + } }; info: Info, announce: []const u8, -pub fn parse(b: bencode.BValue) !MetaInfo { +pub fn parse(a: std.mem.Allocator, b: bencode.BValue) Error!MetaInfo { + // TODO diagnostics var d = b.asDict() catch return error.Malformatted; const i = d.get("info") orelse return error.Malformatted; - const a = d.get("announce") orelse return error.Malformatted; + const an = d.get("announce") orelse return error.Malformatted; return .{ - .info = try Info.parse(i), - .announce = a.asString() catch return error.Malformatted, + .info = try Info.parse(a, i), + .announce = an.asString() catch return error.Malformatted, }; } +pub fn deinit(self: *MetaInfo, a: std.mem.Allocator) void { + self.info.deinit(a); +} + test "sample" { const a = std.testing.allocator; const sample_str = @embedFile("sample.torrent"); var b = try bencode.bdecodeBuf(a, sample_str); defer b.deinit(a); - const mi = try MetaInfo.parse(b); + var mi = try MetaInfo.parse(a, b); + defer mi.deinit(a); try std.testing.expectEqualStrings("http://bittorrent-test-tracker.codecrafters.io/announce", mi.announce); + try std.testing.expectEqual(@as(usize, 1), mi.info.files.len); + try std.testing.expectEqualStrings("sample.txt", mi.info.files[0].name); } |