@@ -130,19 +130,27 @@ pub fn main() anyerror!noreturn {
std.log.info("listening at {}", .{net_server.listen_address});
var server: Server = .{
.gpa = gpa,
.net_server = &net_server,
.static_http_file_server = &static_http_file_server,
.db = &db,
.thread_pool = &thread_pool,
.play_queue = try player.Queue.init(),
.config = config,
.device = device,
};
server.serve();
}
pub const Server = struct {
gpa: Allocator,
net_server: *std.net.Server,
static_http_file_server: *StaticHttpFileServer,
db: *Db,
thread_pool: *std.Thread.Pool,
play_queue: player.Queue,
config: Config,
device: *SoundIo.Device,
pub fn serve(s: *Server) noreturn {
while (true) {
@@ -194,8 +202,11 @@ pub const Server = struct {
const cmd = try messageEnum(msg, protocol.Command);
switch (cmd) {
.enqueue_album => {
// TODO don't access db without a held lock
const album_index = try messageIndex(msg[1..], s.db, Db.Album.Index);
std.log.debug("command: enqueue album {d}", .{album_index});
s.enqueueAlbum(album_index) catch |err| {
std.log.err("failed to enqueue album: {s}", .{@errorName(err)});
};
},
_ => {
std.log.warn("unrecognized command from client: 0x{x}", .{msg[0]});
@@ -244,6 +255,68 @@ pub const Server = struct {
};
try ws.writeMessage(&iovecs);
}
fn enqueueAlbum(s: *Server, album_index: Db.Album.Index) !void {
const gpa = s.gpa;
std.log.debug("command: enqueue album {d}", .{@intFromEnum(album_index)});
const Track = struct {
db_file_index: Db.File.Index,
player_file: *player.File,
fn cleanList(ally: Allocator, track_list: *std.MultiArrayList(@This())) void {
for (track_list.items(.player_file)) |file| file.close();
track_list.deinit(ally);
}
};
var track_list: std.MultiArrayList(Track) = .{};
defer Track.cleanList(gpa, &track_list);
const music_dir = s.config.music_directory.handle;
try track_list.ensureUnusedCapacity(gpa, 1);
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
for (s.db.files.items, 0..) |*db_file, i| {
if (db_file.album == album_index) {
const fs_path = s.db.musicDirPath(db_file, &path_buf);
std.log.debug("opening '{s}'", .{fs_path});
track_list.appendAssumeCapacity(.{
.db_file_index = @enumFromInt(i),
.player_file = try player.File.open(music_dir, fs_path, fs_path),
});
try track_list.ensureUnusedCapacity(gpa, 1);
}
}
const AlbumSortContext = struct {
db: *const Db,
tracks: []const Db.File.Index,
pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
const a_index = @intFromEnum(ctx.tracks[a]);
const b_index = @intFromEnum(ctx.tracks[b]);
return ctx.db.files.items[a_index].track_number < ctx.db.files.items[b_index].track_number;
}
};
track_list.sortUnstable(AlbumSortContext{
.db = s.db,
.tracks = track_list.items(.db_file_index),
});
const playing = p: {
s.play_queue.mutex.lock();
defer s.play_queue.mutex.unlock();
try s.play_queue.files.appendSlice(gpa, track_list.items(.player_file));
break :p s.play_queue.playing.raw;
};
track_list.shrinkRetainingCapacity(0); // ownership transferred to the play queue
if (!playing) {
s.play_queue.start(s.device) catch |err| {
std.log.err("unable to start play queue: {s}", .{@errorName(err)});
};
}
}
};
fn scanDir(db: *Db, gpa: Allocator, db_dir: Db.Path.Index, it: *std.fs.Dir.Iterator) anyerror!void {
@@ -293,6 +366,7 @@ fn scanDir(db: *Db, gpa: Allocator, db_dir: Db.Path.Index, it: *std.fs.Dir.Itera
.title = metadata.title.unwrap() orelse basename,
.artist = metadata.artist,
.album = album,
.track_number = metadata.track_number,
.composer = metadata.composer,
.performer = metadata.performer,
});