Changes
2 changed files (+177/-0)
-
-
@@ -0,0 +1,173 @@// 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 clap = @import("clap"); const core = @import("core"); const connection = @import("../connection.zig"); const State = @import("../State.zig"); const ExitCode = @import("../exit.zig").ExitCode; const parser = .{ .format = clap.parsers.enumeration(core.image.ContentType), .px = clap.parsers.int(usize, 10), .scaling = clap.parsers.enumeration(core.image.ScalingMethod), .image_key = clap.parsers.string, }; const params = clap.parseParamsComptime( \\-h, --help Prints this message to stdout and exits. \\-f, --format <format> Image format. \\Available values are: \\* jpeg \\* png \\-s, --scaling <scaling> Scaling method to use. \\Available values are: \\* fit \\* fill \\* stretch \\-w, --width <px> Required when --scaling is set. \\-h, --height <px> Required when --scaling is set. \\<image_key> \\ ); pub fn run(allocator: std.mem.Allocator, iter: *std.process.ArgIterator) ExitCode { var diag = clap.Diagnostic{}; var res = clap.parseEx(clap.Help, ¶ms, parser, iter, .{ .diagnostic = &diag, .allocator = allocator, }) catch |err| { diag.report(std.io.getStdErr().writer(), err) catch {}; return ExitCode.incorrect_usage; }; defer res.deinit(); if (res.args.help > 0) { clap.help(std.io.getStdOut().writer(), clap.Help, ¶ms, .{}) catch { return ExitCode.stdout_write_failed; }; return ExitCode.ok; } const image_key: []const u8 = res.positionals[0] orelse { std.log.err("image_key is required", .{}); clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{}) catch { return ExitCode.stdout_write_failed; }; return ExitCode.incorrect_usage; }; const image_key_z = allocator.dupeZ(u8, image_key) catch { std.log.err("Unable to allocate memory for image key (Out of memory)", .{}); return ExitCode.out_of_memory; }; defer allocator.free(image_key_z); const opts = core.image.GetOptions.make() orelse { std.log.err("Unable to allocate options struct (Out of memory)", .{}); return ExitCode.out_of_memory; }; _ = opts.retain(); defer opts.release(); if (res.args.format) |format| { opts.setContentType(format); } if (res.args.scaling) |scaling| { const width = res.args.width orelse { std.log.err("--width is required when --scaling is set", .{}); return ExitCode.incorrect_usage; }; const height = res.args.height orelse { std.log.err("--height is required when --scaling is set", .{}); return ExitCode.incorrect_usage; }; opts.setSize(scaling, width, height); } const conn = connection.make(allocator) catch |err| { std.log.err("Unable to establish a connection: {s}", .{@errorName(err)}); return switch (err) { error.OutOfMemory => ExitCode.out_of_memory, else => ExitCode.not_ok, }; }; defer conn.release(); while (true) { const ev = conn.getEvent() orelse { std.log.err("Got empty event, terminating", .{}); return ExitCode.not_ok; }; defer ev.release(); switch (ev.internal.payload) { .connection_error => |err_ev| { std.log.err("Failed to read event: {s}", .{@tagName(err_ev.code)}); return ExitCode.not_ok; }, .connected => { break; }, else => { continue; }, } } const loop_thread = std.Thread.spawn(.{}, consumeEvent, .{conn}) catch |err| { std.log.err("Failed to spawn message read thread: {s}", .{@errorName(err)}); return ExitCode.not_ok; }; defer { conn.disconnect(); loop_thread.join(); } const result = conn.getImage(image_key_z.ptr, opts) orelse { std.log.err("Failed to get image: OutOfMemory", .{}); return ExitCode.out_of_memory; }; defer result.release(); if (result.code != .ok) { std.log.err("Failed to get image: {s}", .{@tagName(result.code)}); return if (result.code == .out_of_memory) ExitCode.out_of_memory else ExitCode.not_ok; } const data = result.image orelse { std.log.err("get_image returned ok result, but image field is null", .{}); return ExitCode.not_ok; }; std.io.getStdOut().writeAll(data.data_ptr[0..data.data_len]) catch |err| { std.log.err("Failed to write to stdouit: {s}", .{@errorName(err)}); return ExitCode.stdout_write_failed; }; return ExitCode.ok; } fn consumeEvent(conn: *core.connection.Connection) void { const ev = conn.getEvent() orelse { return; }; defer ev.release(); }
-
-
-
@@ -21,6 +21,7 @@const ExitCode = @import("./exit.zig").ExitCode; const albums = @import("./commands/albums.zig"); const connect = @import("./commands/connect.zig"); const image = @import("./commands/image.zig"); const playback = @import("./commands/playback.zig"); const server = @import("./commands/server.zig");
-
@@ -47,6 +48,7 @@const Commands = enum { connect, albums, image, playback, server, version,
-
@@ -67,6 +69,7 @@ \\<command>\\Available commands: \\* version ... Prints version to stdout and exits. \\* albums ... List albums in library. \\* image ... Download image. \\* playback ... Display playback state of a zone. \\* server ... Lists or gets information of Roon Server on network \\* connect ... Connects to Roon server and save state to a file.
-
@@ -115,6 +118,7 @@ };switch (command) { .albums => return @intFromEnum(albums.run(allocator, &iter)), .image => return @intFromEnum(image.run(allocator, &iter)), .playback => return @intFromEnum(playback.run(allocator, &iter)), .server => return @intFromEnum(server.run(allocator, &iter)), .version => {
-