diff --git a/src/main.zig b/src/main.zig index 2160b9f..6ce9449 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,10 +1,25 @@ const std = @import("std"); const fs = std.fs; +const mem = std.mem; +const TrackInfo = @import("trackinfo.zig"); + +const TrackMap = std.StringHashMap(TrackInfo); + +fn parse_musicdef(allocator: mem.Allocator, tracks: *TrackMap, file: []const u8) !void { + var lumps = mem.tokenizeSequence(u8, file, "Lump "); + + while (lumps.next()) |lump| { + // Parse lump and add it to the tracklist + const info = try TrackInfo.parseLump(allocator, lump); + try tracks.put(try allocator.dupe(u8, info.id), info); + } +} pub fn main() !void { // Get allocator var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); + defer _ = gpa.deinit(); // Get args const args = try std.process.argsAlloc(allocator); @@ -19,12 +34,21 @@ pub fn main() !void { const folder = args[1]; // Convert folder to absolute - const dir = try fs.cwd().openDir(folder, .{ .iterate = true }); + const dir = try fs.cwd().openDir(folder, .{ + .iterate = true, + }); // Prepare hashmap for storing song locations var trackLocations = std.StringHashMap([]const u8).init(allocator); defer trackLocations.deinit(); + var trackInfos = TrackMap.init(allocator); + defer trackInfos.deinit(); + + var arenaAllocator = std.heap.ArenaAllocator.init(allocator); + defer arenaAllocator.deinit(); + const keyAllocator = arenaAllocator.allocator(); + // Iter through every file inside the directory (recursive) var iter = try dir.walk(allocator); defer iter.deinit(); @@ -35,16 +59,28 @@ pub fn main() !void { } // Remove extension - const extIndex = std.mem.indexOf(u8, entry.basename, "."); + const extIndex = mem.indexOf(u8, entry.basename, "."); const filename = if (extIndex) |index| entry.basename[0..index] else entry.basename; // Check if it's a music definition file (and parse it if so) - if (std.mem.eql(u8, filename, "MUSICDEF")) { - //TODO + if (mem.eql(u8, filename, "MUSICDEF")) { + const filedata = try dir.readFileAlloc(allocator, entry.path, 1048576); + defer allocator.free(filedata); + try parse_musicdef(keyAllocator, &trackInfos, filedata); } else { // Save file to hashmap of resolved files - try trackLocations.put(filename, entry.path); + try trackLocations.put(try keyAllocator.dupe(u8, filename), entry.path); } - std.debug.print("- {s}\n", .{filename}); + } + + // Print detected songs + var tracks = trackInfos.valueIterator(); + while (tracks.next()) |trackInfo| { + std.debug.print("- [{s}] {?s} ({?s}, {?s})\n", .{ + trackInfo.id, + trackInfo.title, + trackInfo.author, + trackInfo.source, + }); } } diff --git a/src/trackinfo.zig b/src/trackinfo.zig new file mode 100644 index 0000000..949744a --- /dev/null +++ b/src/trackinfo.zig @@ -0,0 +1,70 @@ +const std = @import("std"); +const mem = std.mem; + +const Self = @This(); +const InfoKey = enum { + Title, + Author, + Source, + OriginalComposers, +}; + +const source_original = "Dr. Robotnik's Ring Racers"; + +allocator: mem.Allocator, +id: []const u8, +title: ?[]const u8, +author: ?[]const u8, +source: ?[]const u8, +originalComposers: ?[]const u8, +is_original: bool, + +pub fn parseLump(allocator: mem.Allocator, lump: []const u8) !Self { + // Get lines + var lines = mem.tokenize(u8, lump, "\n"); + + // Next line is the ID, we need that ASAP! + const id = mem.trim(u8, lines.next().?, " \r"); + var info = Self{ + .allocator = allocator, + .id = try allocator.dupe(u8, id), + .title = null, + .author = null, + .source = null, + .originalComposers = null, + .is_original = false, + }; + + // Read rest of lump and assign to fields in struct + while (lines.next()) |line| { + const sep = mem.indexOf(u8, line, "="); + if (sep) |sepIndex| { + const key = mem.trim(u8, line[0..sepIndex], " \r"); + const val = mem.trim(u8, line[sepIndex + 1 ..], " \r"); + const ownedVal = try allocator.dupe(u8, val); + const infoKey = std.meta.stringToEnum(InfoKey, key) orelse continue; + switch (infoKey) { + .Title => info.title = ownedVal, + .Author => info.author = ownedVal, + .Source => info.source = ownedVal, + .OriginalComposers => info.originalComposers = ownedVal, + } + } + } + + // Check if it's an original work + if (info.source) |source| { + info.is_original = mem.eql(u8, source, source_original); + } + + return info; +} + +pub fn deinit(self: Self) void { + self.allocator.free(self.id); + if (self.title) |ptr| self.allocator.free(ptr); + if (self.author) |ptr| self.allocator.free(ptr); + if (self.source) |ptr| self.allocator.free(ptr); + if (self.title) |ptr| self.allocator.free(ptr); + if (self.originalComposers) |ptr| self.allocator.free(ptr); +}