srctree

Andrew Kelley parent c2d63a7d ab111763
server obeys enqueue album command

server/main.zig added: 102, removed: 3, total 99
@@ -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,
});
 
shared/Db.zig added: 102, removed: 3, total 99
@@ -144,6 +144,7 @@ pub const File = extern struct {
basename: String,
title: String,
album: Album.Index,
track_number: i16,
composer: OptionalString,
performer: OptionalString,
 
@@ -223,3 +224,27 @@ pub fn addFile(db: *Db, gpa: Allocator, new_file: File) Allocator.Error!File.Ind
pub fn fmtPath(db: *const Db, p: Path) std.fmt.Formatter(Path.format) {
return .{ .data = .{ .path = p, .db = db } };
}
 
pub fn musicDirPath(db: *const Db, file: *const File, buf: []u8) [:0]u8 {
return renderPath(db, .{
.parent = file.directory.toOptional(),
.basename = file.basename,
}, buf);
}
 
fn renderPath(db: *const Db, p: Path, buf: []u8) [:0]u8 {
var i: usize = 0;
if (p.parent.unwrap()) |parent| {
const parent_path = db.directory(parent);
if (parent_path.basename != .empty) {
i += renderPath(db, parent_path, buf).len;
buf[i] = '/';
i += 1;
}
}
const basename = stringToSlice(db, p.basename);
@memcpy(buf[i..][0..basename.len], basename);
i += basename.len;
buf[i] = 0;
return buf[0..i :0];
}