diff --git a/src/MusicBrainzQuery.zig b/src/MusicBrainzQuery.zig index e8d799d..ab01b19 100644 --- a/src/MusicBrainzQuery.zig +++ b/src/MusicBrainzQuery.zig @@ -20,43 +20,44 @@ pub const Result = struct { if (count == 0) return null; const sorted_recordings = self.recordings.?; std.mem.sort(Recording, sorted_recordings, {}, Recording.lessThan); - rc_loop: for (sorted_recordings) |rc| { + for (sorted_recordings) |rc| { switch (pbo.parse) { .song => { - var output = searchIDs{ .id = "" }; - switch (pbo.specifyBy) { - .song => { - if (std.mem.eql(u8, rc.title, smd.tn)) { - output.id = rc.id; - break :rc_loop; - } - }, - .album => { - if (rc.releases) |rls| { - for (rls) |rl| { - if (std.mem.eql(u8, rl.title, smd.rgn)) { - output.id = rc.id; - break :rc_loop; - } + const output = searchIDs{ + .id = switch (pbo.specifyBy) { + .song => song_song: { + if (std.mem.eql(u8, rc.title, smd.tn)) { + break :song_song rc.id; + //break :rc_loop; } - } else { - std.log.err("Could not find any releases", .{}); - } - }, - .artist => { - if (rc.@"artist-credit") |acs| { - for (acs) |ac| { - if (std.mem.eql(u8, ac.name, smd.an)) { - output.id = rc.id; - break :rc_loop; + }, + .album => song_album: { + if (rc.releases) |rls| { + for (rls) |rl| { + if (std.mem.eql(u8, rl.title, smd.rgn)) { + break :song_album rc.id; + //break :rc_loop; + } } + } else { + std.log.err("Could not find any releases", .{}); } - } else { - std.log.err("Could not find any artists", .{}); - } + }, + .artist => song_artist: { + if (rc.@"artist-credit") |acs| { + for (acs) |ac| { + if (std.mem.eql(u8, ac.name, smd.an)) { + break :song_artist rc.id; + //break :rc_loop; + } + } + } else { + std.log.err("Could not find any artists", .{}); + } + }, + .all => unreachable, // I'll do this later }, - .all => unreachable, // I'll do this later - } + }; return output; }, .album => { @@ -133,59 +134,45 @@ pub const parseOption = union(enum) { pub const parseByOptions = struct { parse: parseOption = .song, specifyBy: parseOption = .song }; -const eunuchMetadata = struct { - tn: struct { dec: []const u8, enc: []const u8 = undefined }, - rn: struct { dec: []const u8, enc: []const u8 = undefined }, - an: struct { dec: []const u8, enc: []const u8 = undefined }, +pub const searchMetadata = struct { + tn: []const u8, + rn: []const u8, + an: []const u8, }; // Maybe just use a HashMap... -pub const searchMetadata = struct { - alloc: std.mem.Allocator = undefined, - tn: struct { dec: []const u8, enc: []const u8 = undefined }, - rn: struct { dec: []const u8, enc: []const u8 = undefined }, - an: struct { dec: []const u8, enc: []const u8 = undefined }, +pub const searchMetadataEncoded = struct { + tn: []const u8, + rn: []const u8, + an: []const u8, - pub fn deinit(self: *searchMetadata) void { - self.alloc.free(self.tn.enc); - self.alloc.free(self.rn.enc); - self.alloc.free(self.an.enc); - } - - pub fn percentEncode(self: *searchMetadata) !void { - inline for (std.meta.fields(eunuchMetadata)) |k| { - var encoded = std.ArrayList(u8).init(self.alloc); - errdefer encoded.deinit(); - for (@field(self, k.name).dec) |v| { - if ((v >= 'A' and v <= 'Z') or (v >= 'a' and v <= 'z') or (v >= '0' and v <= '9') or v == '-' or v == '_' or v == '.' or v == '~') { - try encoded.append(v); - } else if (v == ' ') { - try encoded.append('+'); - } else { - const hex = try std.fmt.allocPrint(self.alloc, "%{x}", .{v}); - defer self.alloc.free(hex); - try encoded.appendSlice(hex); - } - } - @field(self, k.name).enc = try encoded.toOwnedSlice(); - } - } + //pub fn deinit(self: *searchMetadata) void { + // self.alloc.free(self.tn.enc); + // self.alloc.free(self.rn.enc); + // self.alloc.free(self.an.enc); + //} }; +pub fn percentEncode(alloc: std.mem.Allocator, smd: searchMetadata) !searchMetadataEncoded { + var output: searchMetadataEncoded = undefined; + inline for (std.meta.fields(searchMetadata)) |k| { + var encoded = std.ArrayList(u8).init(alloc); + errdefer encoded.deinit(); + for (@field(smd, k.name)) |v| { + if ((v >= 'A' and v <= 'Z') or (v >= 'a' and v <= 'z') or (v >= '0' and v <= '9') or v == '-' or v == '_' or v == '.' or v == '~') { + try encoded.append(v); + } else if (v == ' ') { + try encoded.append('+'); + } else { + const hex = try std.fmt.allocPrint(alloc, "%{x}", .{v}); + defer alloc.free(hex); + try encoded.appendSlice(hex); + } + } + @field(output, k.name) = try encoded.toOwnedSlice(); + } -// I don't think I need a second type, but I'd like to hold on to the original, unencoded metadata as well, so maybe this is fine -// Maybe make fields for encoded data? -//pub const searchMetadataEncoded = struct { -// alloc: std.mem.Allocator, -// tn: []const u8, -// rgn: []const u8, -// an: []const u8, -// -// pub fn deinit(self: *const searchMetadataEncoded) void { -// self.alloc.free(self.an); -// self.alloc.free(self.rgn); -// self.alloc.free(self.tn); -// } -//}; + return output; +} pub const searchIDs = union { id: []const u8, diff --git a/src/root.zig b/src/root.zig index 4732427..760addf 100644 --- a/src/root.zig +++ b/src/root.zig @@ -8,57 +8,53 @@ const Client = std.http.Client; pub const user_agent: []const u8 = "ZuletztMBClient/0.0.1 (swebbguy@gmail.com)"; -pub fn mbSearch(allocator: std.mem.Allocator, ar: *std.ArrayList(u8), smd: MBQ.searchMetadata) !?[]const u8 { - const encoded = try MBQ.percentEncode(allocator, smd); - defer encoded.deinit(); - var client = Client{ .allocator = allocator }; +pub fn mbSearch(alloc: std.mem.Allocator, smd: MBQ.searchMetadata) !?MBQ.Result { + const encoded = try MBQ.percentEncode(alloc, smd); + + // Might consider making just one of these and passing it around + var client = Client{ .allocator = alloc }; defer client.deinit(); - const query: []const u8 = try std.fmt.allocPrint(allocator, "https://musicbrainz.org/ws/2/recording/?query=\"{s}\"%20AND%20artist:\"{s}\"%20AND%20release:\"{s}\"&fmt=json", .{ encoded.tn, encoded.an, encoded.rgn }); - defer allocator.free(query); + const query: []const u8 = try std.fmt.allocPrint(alloc, "https://musicbrainz.org/ws/2/recording/?query=\"{s}\"%20AND%20artist:\"{s}\"%20AND%20release:\"{s}\"&fmt=json", .{ encoded.tn, encoded.an, encoded.rn }); + defer alloc.free(query); - const response: Client.FetchResult = try client.fetch(.{ .response_storage = .{ .dynamic = ar }, .location = .{ .url = query }, .method = .GET, .headers = .{ .user_agent = .{ .override = user_agent } } }); + var ar = std.ArrayList(u8).init(alloc); + errdefer ar.deinit(); + + const response: Client.FetchResult = try client.fetch(.{ .response_storage = .{ .dynamic = &ar }, .location = .{ .url = query }, .method = .GET, .headers = .{ .user_agent = .{ .override = user_agent } } }); switch (@intFromEnum(response.status)) { - 0...299 => std.log.debug("Success\n", .{}), + 0...299 => std.log.debug("Success", .{}), 300...399 => { - std.log.err("Redirected\n", .{}); + std.log.err("Redirected", .{}); return null; }, 400...511 => { - std.log.err("Get rekt\n{s}", .{ar.items}); + std.log.err("Get rekt {s}", .{ar.items}); return Client.ConnectError.ConnectionRefused; }, 512 => { - std.log.err("Need to login\n", .{}); + std.log.err("Need to login", .{}); return null; }, else => unreachable, } - return ar.items; + const json = try std.json.parseFromSlice(MBQ.Result, alloc, ar.items, .{ .ignore_unknown_fields = true }); + errdefer json.deinit(); + + return json.value; } -// This test is very volatile, but I think -// these params are specific enough that -// it shouldn't need changing too often test "arid_via_recording" { const test_alloc = std.testing.allocator; - const metadata = MBQ.searchMetadata{ .tn = "Veni veni Emmanuel", .an = "iamthemorning", .rgn = "Counting the Ghosts" }; + const metadata = MBQ.searchMetadata{ .tn = "Veni veni Emmanuel", .an = "iamthemorning", .rn = "Counting the Ghosts" }; const iatm_id: []const u8 = "5854a6de-af8f-4b99-8710-cb47d6436a19"; - var mb_result = std.ArrayList(u8).init(test_alloc); - defer mb_result.deinit(); - - const search_result = try mbSearch(test_alloc, &mb_result, metadata); - - if (search_result) |sr| { - const json = try std.json.parseFromSlice(MBQ.Result, test_alloc, sr, .{ .ignore_unknown_fields = true }); - defer json.deinit(); - - const result: MBQ.Result = json.value; - const id = result.getIDs(metadata, .{ .parse = .artist, .specifyBy = .song }); + const search_result = try mbSearch(test_alloc, metadata); + if (search_result) |rs| { + const id = rs.getIDs(metadata, .{ .parse = .artist, .specifyBy = .song }); std.Thread.sleep(1000000000); @@ -71,7 +67,7 @@ test "arid_via_recording" { test "arid_via_recording_multiple_artists_1" { const test_alloc = std.testing.allocator; - const metadata = MBQ.searchMetadata{ .tn = "Roll Me Up And Smoke Me When I Die", .an = "Lyle Lovett", .rgn = "Willie Nelson American Outlaw" }; + const metadata = MBQ.searchMetadata{ .alloc = test_alloc, .tn = .{ .dec = "Roll Me Up And Smoke Me When I Die" }, .an = .{ .dec = "Lyle Lovett" }, .rn = .{ .dec = "Willie Nelson American Outlaw" } }; const ll_id: []const u8 = "7241e3ed-5ad4-4849-94df-6858ea833472"; var mb_result = std.ArrayList(u8).init(test_alloc); @@ -97,7 +93,7 @@ test "arid_via_recording_multiple_artists_1" { test "rgid_via_recording" { const test_alloc = std.testing.allocator; - const metadata = MBQ.searchMetadata{ .tn = "I Of The Storm", .rgn = "Beneath the Skin", .an = "Of Monsters and Men" }; + const metadata = MBQ.searchMetadata{ .alloc = test_alloc, .tn = .{ .dec = "I Of The Storm" }, .rn = .{ .dec = "Beneath the Skin" }, .an = .{ .dec = "Of Monsters and Men" } }; const bts_id: []const u8 = "4f4f5b98-45ac-4b47-addb-66b501473bd8"; var mb_result = std.ArrayList(u8).init(test_alloc); @@ -123,7 +119,7 @@ test "rgid_via_recording" { test "rgid_via_recording_multiple_artists_2" { const test_alloc = std.testing.allocator; - const metadata = MBQ.searchMetadata{ .tn = "Hesitating Beauty", .rgn = "Mermaid Avenue", .an = "Wilco" }; + const metadata = MBQ.searchMetadata{ .alloc = test_alloc, .tn = .{ .dec = "Hesitating Beauty" }, .rn = .{ .dec = "Mermaid Avenue" }, .an = .{ .dec = "Wilco" } }; const wilco_id: []const u8 = "9ba73bf8-6c15-4bd2-8da1-e2292538f617"; var mb_result = std.ArrayList(u8).init(test_alloc); @@ -150,7 +146,7 @@ test "rgid_via_recording_multiple_artists_2" { test "rid_aqualung" { const test_alloc = std.testing.allocator; - const metadata = MBQ.searchMetadata{ .tn = "Aqualung", .rgn = "Aqualung", .an = "Jethro Tull" }; + const metadata = MBQ.searchMetadata{ .alloc = test_alloc, .tn = .{ .dec = "Aqualung" }, .rn = .{ .dec = "Aqualung" }, .an = .{ .dec = "Jethro Tull" } }; const aqualung_id: []const u8 = "2621d113-3a9f-41f9-a0f2-b9459f86e8e9"; var mb_result = std.ArrayList(u8).init(test_alloc); @@ -174,7 +170,7 @@ test "rid_aqualung" { test "rid_battery" { const test_alloc = std.testing.allocator; - const metadata = MBQ.searchMetadata{ .tn = "Battery", .rgn = "Master of Puppets", .an = "Metallica" }; + const metadata = MBQ.searchMetadata{ .alloc = test_alloc, .tn = .{ .dec = "Battery" }, .rn = .{ .dec = "Master of Puppets" }, .an = .{ .dec = "Metallica" } }; const battery_id: []const u8 = "3bfda26a-49fa-4bc4-a4d6-8bbfa0767ab7"; var mb_result = std.ArrayList(u8).init(test_alloc);