Changes
10 changed files (+516/-522)
-
-
@@ -18,7 +18,7 @@ const std = @import("std");const Arc = @import("./Arc.zig"); const freelog = @import("./log.zig").freelog; const BrowseService = @import("./services/browse.zig").BrowseService; const BrowseService = @import("./services/BrowseService.zig"); pub const Hierarchy = enum(c_int) { browse = 0,
-
-
-
@@ -25,11 +25,11 @@ const discovery = @import("./discovery.zig");const extension = @import("./extension.zig").extension; const image = @import("./image.zig"); const freelog = @import("./log.zig").freelog; const BrowseService = @import("./services/browse.zig").BrowseService; const BrowseService = @import("./services/BrowseService.zig"); const ImageService = @import("./services/ImageService.zig"); const ping = @import("./services/ping.zig"); const PingService = @import("./services/PingService.zig"); const registry = @import("./services/registry.zig"); const TransportService = @import("./services/transport.zig").TransportService; const TransportService = @import("./services/TransportService.zig"); const transport = @import("./transport.zig"); pub const ConnectionError = enum(c_int) {
-
@@ -801,7 +801,7 @@ std.log.warn("Failed to parse MOO metadata: {s}", .{@errorName(err)});continue; }; if (std.mem.eql(u8, ping.PingService.ping_id, meta.service)) { if (std.mem.eql(u8, PingService.ping_id, meta.service)) { writePong(ws, header_ctx, msg.data) catch |err| { std.log.warn( "Failed to respond to ping request: {s}",
-
@@ -830,7 +830,7 @@ ) !void {var buffer = std.ArrayList(u8).init(allocator); defer buffer.deinit(); try ping.PingService.ping(buffer.writer(), header_ctx, message); try PingService.ping(buffer.writer(), header_ctx, message); try ws.writeBin(buffer.items); }
-
-
-
@@ -15,10 +15,10 @@ //// SPDX-License-Identifier: Apache-2.0 const Extension = @import("./services/registry.zig").Extension; const BrowseService = @import("./services/browse.zig").BrowseService; const BrowseService = @import("./services/BrowseService.zig"); const ImageService = @import("./services/ImageService.zig"); const PingService = @import("./services/ping.zig").PingService; const TransportService = @import("./services/transport.zig").TransportService; const PingService = @import("./services/PingService.zig"); const TransportService = @import("./services/TransportService.zig"); pub const extension = Extension{ .id = "jp.pocka.plac",
-
-
-
@@ -0,0 +1,257 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const moo = @import("moo"); pub const id = "com.roonlabs.browse:1"; pub const Item = struct { title: []const u8, subtitle: ?[]const u8 = null, image_key: ?[]const u8 = null, item_key: ?[]const u8 = null, hint: Hint = .unknown, input_prompt: ?InputPrompt = null, pub const Hint = enum { unknown, action, action_list, list, header, pub fn jsonParseFromValue( _: std.mem.Allocator, value: std.json.Value, _: std.json.ParseOptions, ) std.json.ParseFromValueError!@This() { return switch (value) { .string => |v| { inline for (@typeInfo(@This()).@"enum".fields) |field| { if (std.mem.eql(u8, field.name, "unknown")) { continue; } if (std.mem.eql(u8, field.name, v)) { return @enumFromInt(field.value); } } return .unknown; }, else => .unknown, }; } }; pub const InputPrompt = struct { prompt: []const u8, action: []const u8, value: ?[]const u8 = null, is_password: bool = false, }; }; pub const List = struct { title: []const u8, count: usize = 0, subtitle: ?[]const u8 = null, image_key: ?[]const u8 = null, level: usize = 0, display_offset: ?i64 = null, hint: Hint = .unknown, pub const Hint = enum { unknown, action_list, pub fn jsonParseFromValue( _: std.mem.Allocator, value: std.json.Value, _: std.json.ParseOptions, ) std.json.ParseFromValueError!@This() { return switch (value) { .string => |v| { inline for (@typeInfo(@This()).@"enum".fields) |field| { if (std.mem.eql(u8, field.name, "unknown")) { continue; } if (std.mem.eql(u8, field.name, v)) { return @enumFromInt(field.value); } } return .unknown; }, else => .unknown, }; } }; }; pub const Browse = struct { const method = "/browse"; pub const Request = struct { hierarchy: []const u8, item_key: ?[]const u8 = null, input: ?[]const u8 = null, zone_or_output_id: ?[]const u8 = null, pop_all: ?bool = null, pop_levels: ?usize = null, refresh_list: ?bool = null, /// Caller owns the returned memory pub fn encode( self: Request, allocator: std.mem.Allocator, request_id: u64, ) ![]u8 { const meta = moo.Metadata{ .service = id ++ method, .verb = "REQUEST", }; const body = moo.JsonBody(Request).init(&self, .{ .emit_null_optional_fields = false, }); const header = body.getHeader(request_id); const buf = try allocator.alloc( u8, meta.getEncodeSize() + header.getEncodeSize() + body.getEncodeSize(), ); errdefer allocator.free(buf); var fbs = std.io.fixedBufferStream(buf); try moo.encode(fbs.writer(), meta, header, body); return buf; } }; pub const Response = struct { action: Action, item: ?Item = null, list: ?List = null, message: ?[]const u8 = null, is_error: ?bool = null, const Action = enum { message, none, list, replace_item, remove_item, }; pub const Error = error{ NonSuccessResponse, }; pub fn parse( allocator: std.mem.Allocator, meta: *const moo.Metadata, header_ctx: moo.HeaderParsingContext, message: []const u8, ) !moo.JsonBody(Response) { if (!std.mem.eql(u8, meta.service, "Success")) { std.log.err("Expected \"Success\" for {s} endpoint, got \"{s}\"", .{ method, meta.service }); return Error.NonSuccessResponse; } _, const body_ctx = try moo.WellKnownHeaders.parse(message, header_ctx); return try moo.JsonBody(Response).parse(allocator, message, body_ctx, .{ .ignore_unknown_fields = true, .allocate = .alloc_always, }); } }; }; pub const Load = struct { const method = "/load"; pub const Request = struct { set_display_offset: ?i64 = null, level: ?usize = 0, offset: ?i64 = 0, count: ?usize = 0, hierarchy: []const u8, multi_session_key: ?[]const u8 = null, /// Caller owns the returned memory pub fn encode( self: Request, allocator: std.mem.Allocator, request_id: u64, ) ![]u8 { const meta = moo.Metadata{ .service = id ++ method, .verb = "REQUEST", }; const body = moo.JsonBody(Request).init(&self, .{ .emit_null_optional_fields = false, }); const header = body.getHeader(request_id); const buf = try allocator.alloc( u8, meta.getEncodeSize() + header.getEncodeSize() + body.getEncodeSize(), ); errdefer allocator.free(buf); var fbs = std.io.fixedBufferStream(buf); try moo.encode(fbs.writer(), meta, header, body); return buf; } }; pub const Response = struct { items: []const Item, offset: i64 = 0, list: List, pub const Error = error{ NonSuccessResponse, }; pub fn parse( allocator: std.mem.Allocator, meta: *const moo.Metadata, header_ctx: moo.HeaderParsingContext, message: []const u8, ) !moo.JsonBody(Response) { if (!std.mem.eql(u8, meta.service, "Success")) { std.log.err("Expected \"Success\" for {s} endpoint, got \"{s}\"", .{ method, meta.service }); return Error.NonSuccessResponse; } _, const body_ctx = try moo.WellKnownHeaders.parse(message, header_ctx); return try moo.JsonBody(Response).parse(allocator, message, body_ctx, .{ .ignore_unknown_fields = true, .allocate = .alloc_always, }); } }; };
-
-
-
@@ -0,0 +1,37 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const moo = @import("moo"); pub const id = "com.roonlabs.ping:1"; pub const ping_id = id ++ "/ping"; pub fn ping(writer: anytype, parser_ctx: moo.HeaderParsingContext, message: []const u8) !void { const req_header, _ = try moo.NoBodyHeaders.parse(message, parser_ctx); const meta = moo.Metadata{ .service = "Success", .verb = "COMPLETE", }; const body = moo.NoBody{}; const header = body.getHeader(req_header.request_id); try moo.encode(writer, meta, header, body); }
-
-
-
@@ -0,0 +1,212 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const moo = @import("moo"); pub const id = "com.roonlabs.transport:2"; pub const NowPlaying = struct { seek_position: ?u64, length: ?u64, image_key: ?[]const u8 = null, one_line: struct { line1: []const u8, }, two_line: struct { line1: []const u8, line2: ?[]const u8 = null, }, three_line: struct { line1: []const u8, line2: ?[]const u8 = null, line3: ?[]const u8 = null, }, }; pub const PlaybackState = enum { playing, paused, loading, stopped, pub fn jsonParseFromValue( _: std.mem.Allocator, value: std.json.Value, _: std.json.ParseOptions, ) std.json.ParseFromValueError!@This() { return switch (value) { .string => |v| { inline for (@typeInfo(@This()).@"enum".fields) |field| { if (std.mem.eql(u8, field.name, v)) { return @enumFromInt(field.value); } } return std.json.ParseFromValueError.InvalidEnumTag; }, else => std.json.ParseFromValueError.UnexpectedToken, }; } }; pub const Zone = struct { zone_id: []const u8, display_name: []const u8, now_playing: ?NowPlaying = null, state: PlaybackState, is_next_allowed: bool = false, is_previous_allowed: bool = false, is_pause_allowed: bool = false, is_play_allowed: bool = false, is_seek_allowed: bool = false, }; pub const SeekChange = struct { zone_id: []const u8, seek_position: ?u64 = null, }; pub const SubscribeZoneChanges = struct { pub const Request = struct { subscription_key: []const u8, }; pub fn request(allocator: std.mem.Allocator, request_id: u64, subscription_id: u64) ![]u8 { const meta = moo.Metadata{ .service = id ++ "/subscribe_zones", .verb = "REQUEST", }; var sub_id_buf: [std.fmt.count("{}", .{std.math.maxInt(u64)})]u8 = undefined; var sub_id_fbs = std.io.fixedBufferStream(&sub_id_buf); try std.fmt.format(sub_id_fbs.writer(), "{}", .{subscription_id}); const body = moo.JsonBody(Request).init(&.{ .subscription_key = sub_id_fbs.getWritten(), }, .{ .emit_null_optional_fields = false, }); const header = body.getHeader(request_id); const buf = try allocator.alloc( u8, meta.getEncodeSize() + header.getEncodeSize() + body.getEncodeSize(), ); errdefer allocator.free(buf); var fbs = std.io.fixedBufferStream(buf); try moo.encode(fbs.writer(), meta, header, body); return buf; } pub const InitialResponse = struct { zones: []const Zone, }; pub fn initialResponse( allocator: std.mem.Allocator, header_ctx: moo.HeaderParsingContext, message: []const u8, ) !moo.JsonBody(InitialResponse) { _, const body_ctx = try moo.WellKnownHeaders.parse(message, header_ctx); return try moo.JsonBody(InitialResponse).parse(allocator, message, body_ctx, .{ .ignore_unknown_fields = true, .allocate = .alloc_always, }); } pub const Response = struct { zones_removed: []const []const u8 = &.{}, zones_added: []const Zone = &.{}, zones_changed: []const Zone = &.{}, zones_seek_changed: []const SeekChange = &.{}, }; pub fn response( allocator: std.mem.Allocator, header_ctx: moo.HeaderParsingContext, message: []const u8, ) !moo.JsonBody(Response) { _, const body_ctx = try moo.WellKnownHeaders.parse(message, header_ctx); return try moo.JsonBody(Response).parse(allocator, message, body_ctx, .{ .ignore_unknown_fields = true, .allocate = .alloc_always, }); } }; pub const Control = enum { play, pause, playpause, stop, previous, next, const method = "/control"; pub const Request = struct { zone_or_output_id: []const u8, control: []const u8, }; /// Caller owns the returned memory pub fn request( self: Control, allocator: std.mem.Allocator, request_id: u64, zone_id: []const u8, ) ![]u8 { const meta = moo.Metadata{ .service = id ++ method, .verb = "REQUEST", }; const body = moo.JsonBody(Request).init(&Request{ .zone_or_output_id = zone_id, .control = @tagName(self), }, .{}); const header = body.getHeader(request_id); const buf = try allocator.alloc( u8, meta.getEncodeSize() + header.getEncodeSize() + body.getEncodeSize(), ); errdefer allocator.free(buf); var fbs = std.io.fixedBufferStream(buf); try moo.encode(fbs.writer(), meta, header, body); return buf; } pub const ResponseError = error{ NonSuccessResponse, }; pub fn response( meta: *const moo.Metadata, ) ResponseError!void { if (!std.mem.eql(u8, meta.service, "Success")) { std.log.err("Expected \"Success\" for {s} endpoint, got \"{s}\"", .{ method, meta.service }); return ResponseError.NonSuccessResponse; } } };
-
-
core/src/services/browse.zig (deleted)
-
@@ -1,259 +0,0 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const moo = @import("moo"); pub const BrowseService = struct { pub const id = "com.roonlabs.browse:1"; pub const Item = struct { title: []const u8, subtitle: ?[]const u8 = null, image_key: ?[]const u8 = null, item_key: ?[]const u8 = null, hint: Hint = .unknown, input_prompt: ?InputPrompt = null, pub const Hint = enum { unknown, action, action_list, list, header, pub fn jsonParseFromValue( _: std.mem.Allocator, value: std.json.Value, _: std.json.ParseOptions, ) std.json.ParseFromValueError!@This() { return switch (value) { .string => |v| { inline for (@typeInfo(@This()).@"enum".fields) |field| { if (std.mem.eql(u8, field.name, "unknown")) { continue; } if (std.mem.eql(u8, field.name, v)) { return @enumFromInt(field.value); } } return .unknown; }, else => .unknown, }; } }; pub const InputPrompt = struct { prompt: []const u8, action: []const u8, value: ?[]const u8 = null, is_password: bool = false, }; }; pub const List = struct { title: []const u8, count: usize = 0, subtitle: ?[]const u8 = null, image_key: ?[]const u8 = null, level: usize = 0, display_offset: ?i64 = null, hint: Hint = .unknown, pub const Hint = enum { unknown, action_list, pub fn jsonParseFromValue( _: std.mem.Allocator, value: std.json.Value, _: std.json.ParseOptions, ) std.json.ParseFromValueError!@This() { return switch (value) { .string => |v| { inline for (@typeInfo(@This()).@"enum".fields) |field| { if (std.mem.eql(u8, field.name, "unknown")) { continue; } if (std.mem.eql(u8, field.name, v)) { return @enumFromInt(field.value); } } return .unknown; }, else => .unknown, }; } }; }; pub const Browse = struct { const method = "/browse"; pub const Request = struct { hierarchy: []const u8, item_key: ?[]const u8 = null, input: ?[]const u8 = null, zone_or_output_id: ?[]const u8 = null, pop_all: ?bool = null, pop_levels: ?usize = null, refresh_list: ?bool = null, /// Caller owns the returned memory pub fn encode( self: Request, allocator: std.mem.Allocator, request_id: u64, ) ![]u8 { const meta = moo.Metadata{ .service = id ++ method, .verb = "REQUEST", }; const body = moo.JsonBody(Request).init(&self, .{ .emit_null_optional_fields = false, }); const header = body.getHeader(request_id); const buf = try allocator.alloc( u8, meta.getEncodeSize() + header.getEncodeSize() + body.getEncodeSize(), ); errdefer allocator.free(buf); var fbs = std.io.fixedBufferStream(buf); try moo.encode(fbs.writer(), meta, header, body); return buf; } }; pub const Response = struct { action: Action, item: ?Item = null, list: ?List = null, message: ?[]const u8 = null, is_error: ?bool = null, const Action = enum { message, none, list, replace_item, remove_item, }; pub const Error = error{ NonSuccessResponse, }; pub fn parse( allocator: std.mem.Allocator, meta: *const moo.Metadata, header_ctx: moo.HeaderParsingContext, message: []const u8, ) !moo.JsonBody(Response) { if (!std.mem.eql(u8, meta.service, "Success")) { std.log.err("Expected \"Success\" for {s} endpoint, got \"{s}\"", .{ method, meta.service }); return Error.NonSuccessResponse; } _, const body_ctx = try moo.WellKnownHeaders.parse(message, header_ctx); return try moo.JsonBody(Response).parse(allocator, message, body_ctx, .{ .ignore_unknown_fields = true, .allocate = .alloc_always, }); } }; }; pub const Load = struct { const method = "/load"; pub const Request = struct { set_display_offset: ?i64 = null, level: ?usize = 0, offset: ?i64 = 0, count: ?usize = 0, hierarchy: []const u8, multi_session_key: ?[]const u8 = null, /// Caller owns the returned memory pub fn encode( self: Request, allocator: std.mem.Allocator, request_id: u64, ) ![]u8 { const meta = moo.Metadata{ .service = id ++ method, .verb = "REQUEST", }; const body = moo.JsonBody(Request).init(&self, .{ .emit_null_optional_fields = false, }); const header = body.getHeader(request_id); const buf = try allocator.alloc( u8, meta.getEncodeSize() + header.getEncodeSize() + body.getEncodeSize(), ); errdefer allocator.free(buf); var fbs = std.io.fixedBufferStream(buf); try moo.encode(fbs.writer(), meta, header, body); return buf; } }; pub const Response = struct { items: []const Item, offset: i64 = 0, list: List, pub const Error = error{ NonSuccessResponse, }; pub fn parse( allocator: std.mem.Allocator, meta: *const moo.Metadata, header_ctx: moo.HeaderParsingContext, message: []const u8, ) !moo.JsonBody(Response) { if (!std.mem.eql(u8, meta.service, "Success")) { std.log.err("Expected \"Success\" for {s} endpoint, got \"{s}\"", .{ method, meta.service }); return Error.NonSuccessResponse; } _, const body_ctx = try moo.WellKnownHeaders.parse(message, header_ctx); return try moo.JsonBody(Response).parse(allocator, message, body_ctx, .{ .ignore_unknown_fields = true, .allocate = .alloc_always, }); } }; }; };
-
-
core/src/services/ping.zig (deleted)
-
@@ -1,39 +0,0 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const moo = @import("moo"); pub const PingService = struct { pub const id = "com.roonlabs.ping:1"; pub const ping_id = id ++ "/ping"; pub fn ping(writer: anytype, parser_ctx: moo.HeaderParsingContext, message: []const u8) !void { const req_header, _ = try moo.NoBodyHeaders.parse(message, parser_ctx); const meta = moo.Metadata{ .service = "Success", .verb = "COMPLETE", }; const body = moo.NoBody{}; const header = body.getHeader(req_header.request_id); try moo.encode(writer, meta, header, body); } };
-
-
core/src/services/transport.zig (deleted)
-
@@ -1,214 +0,0 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const moo = @import("moo"); pub const TransportService = struct { pub const id = "com.roonlabs.transport:2"; pub const NowPlaying = struct { seek_position: ?u64, length: ?u64, image_key: ?[]const u8 = null, one_line: struct { line1: []const u8, }, two_line: struct { line1: []const u8, line2: ?[]const u8 = null, }, three_line: struct { line1: []const u8, line2: ?[]const u8 = null, line3: ?[]const u8 = null, }, }; pub const PlaybackState = enum { playing, paused, loading, stopped, pub fn jsonParseFromValue( _: std.mem.Allocator, value: std.json.Value, _: std.json.ParseOptions, ) std.json.ParseFromValueError!@This() { return switch (value) { .string => |v| { inline for (@typeInfo(@This()).@"enum".fields) |field| { if (std.mem.eql(u8, field.name, v)) { return @enumFromInt(field.value); } } return std.json.ParseFromValueError.InvalidEnumTag; }, else => std.json.ParseFromValueError.UnexpectedToken, }; } }; pub const Zone = struct { zone_id: []const u8, display_name: []const u8, now_playing: ?NowPlaying = null, state: PlaybackState, is_next_allowed: bool = false, is_previous_allowed: bool = false, is_pause_allowed: bool = false, is_play_allowed: bool = false, is_seek_allowed: bool = false, }; pub const SeekChange = struct { zone_id: []const u8, seek_position: ?u64 = null, }; pub const SubscribeZoneChanges = struct { pub const Request = struct { subscription_key: []const u8, }; pub fn request(allocator: std.mem.Allocator, request_id: u64, subscription_id: u64) ![]u8 { const meta = moo.Metadata{ .service = id ++ "/subscribe_zones", .verb = "REQUEST", }; var sub_id_buf: [std.fmt.count("{}", .{std.math.maxInt(u64)})]u8 = undefined; var sub_id_fbs = std.io.fixedBufferStream(&sub_id_buf); try std.fmt.format(sub_id_fbs.writer(), "{}", .{subscription_id}); const body = moo.JsonBody(Request).init(&.{ .subscription_key = sub_id_fbs.getWritten(), }, .{ .emit_null_optional_fields = false, }); const header = body.getHeader(request_id); const buf = try allocator.alloc( u8, meta.getEncodeSize() + header.getEncodeSize() + body.getEncodeSize(), ); errdefer allocator.free(buf); var fbs = std.io.fixedBufferStream(buf); try moo.encode(fbs.writer(), meta, header, body); return buf; } pub const InitialResponse = struct { zones: []const Zone, }; pub fn initialResponse( allocator: std.mem.Allocator, header_ctx: moo.HeaderParsingContext, message: []const u8, ) !moo.JsonBody(InitialResponse) { _, const body_ctx = try moo.WellKnownHeaders.parse(message, header_ctx); return try moo.JsonBody(InitialResponse).parse(allocator, message, body_ctx, .{ .ignore_unknown_fields = true, .allocate = .alloc_always, }); } pub const Response = struct { zones_removed: []const []const u8 = &.{}, zones_added: []const Zone = &.{}, zones_changed: []const Zone = &.{}, zones_seek_changed: []const SeekChange = &.{}, }; pub fn response( allocator: std.mem.Allocator, header_ctx: moo.HeaderParsingContext, message: []const u8, ) !moo.JsonBody(Response) { _, const body_ctx = try moo.WellKnownHeaders.parse(message, header_ctx); return try moo.JsonBody(Response).parse(allocator, message, body_ctx, .{ .ignore_unknown_fields = true, .allocate = .alloc_always, }); } }; pub const Control = enum { play, pause, playpause, stop, previous, next, const method = "/control"; pub const Request = struct { zone_or_output_id: []const u8, control: []const u8, }; /// Caller owns the returned memory pub fn request( self: Control, allocator: std.mem.Allocator, request_id: u64, zone_id: []const u8, ) ![]u8 { const meta = moo.Metadata{ .service = id ++ method, .verb = "REQUEST", }; const body = moo.JsonBody(Request).init(&Request{ .zone_or_output_id = zone_id, .control = @tagName(self), }, .{}); const header = body.getHeader(request_id); const buf = try allocator.alloc( u8, meta.getEncodeSize() + header.getEncodeSize() + body.getEncodeSize(), ); errdefer allocator.free(buf); var fbs = std.io.fixedBufferStream(buf); try moo.encode(fbs.writer(), meta, header, body); return buf; } pub const ResponseError = error{ NonSuccessResponse, }; pub fn response( meta: *const moo.Metadata, ) ResponseError!void { if (!std.mem.eql(u8, meta.service, "Success")) { std.log.err("Expected \"Success\" for {s} endpoint, got \"{s}\"", .{ method, meta.service }); return ResponseError.NonSuccessResponse; } } }; };
-
-
-
@@ -17,7 +17,7 @@const std = @import("std"); const Arc = @import("./Arc.zig"); const TransportService = @import("./services/transport.zig").TransportService; const TransportService = @import("./services/TransportService.zig"); const freelog = @import("./log.zig").freelog; pub const NowPlaying = extern struct {
-