mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
refactor(cli): extract response-file IPC into exchange submodule
Pull the JSON response-file layer out of cli/mod.rs into a new cli/exchange.rs: - cli_*_path helpers (snapshot, library, server, search, audio_device) — XDG_RUNTIME_DIR-aware path resolvers - write_cli_snapshot - write_library_cli_response, write_server_list_cli_response, write_search_cli_response, write_audio_device_cli_response - read_*_cli_response_blocking pollers (private to cli) - print_*_cli_stdout wrappers (private to cli; thin "JSON or human" switches around the presenter functions) Externally-visible names (cli::write_cli_snapshot, the four write_*_cli_response, the path helpers) preserved via `pub use exchange::*` re-export at the cli root. Internal-only functions demoted to `pub(super)`. cli/mod.rs: 895 → 696 LOC. Behaviour-preserving — same file paths, same atomic write-via-tempfile pattern, same poll intervals + ready predicates.
This commit is contained in:
@@ -0,0 +1,228 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use super::presenters::{print_library_human, print_search_human, print_server_list_human};
|
||||||
|
|
||||||
|
// ─── Response file paths ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// JSON snapshot path (written by the GUI process, read by `psysonic --info`).
|
||||||
|
pub fn cli_snapshot_path() -> PathBuf {
|
||||||
|
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
||||||
|
if !dir.is_empty() {
|
||||||
|
return PathBuf::from(dir).join("psysonic-cli-snapshot.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::env::temp_dir().join("psysonic-cli-snapshot.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli_audio_device_response_path() -> PathBuf {
|
||||||
|
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
||||||
|
if !dir.is_empty() {
|
||||||
|
return PathBuf::from(dir).join("psysonic-cli-audio-devices.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::env::temp_dir().join("psysonic-cli-audio-devices.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli_library_response_path() -> PathBuf {
|
||||||
|
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
||||||
|
if !dir.is_empty() {
|
||||||
|
return PathBuf::from(dir).join("psysonic-cli-library.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::env::temp_dir().join("psysonic-cli-library.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli_server_list_path() -> PathBuf {
|
||||||
|
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
||||||
|
if !dir.is_empty() {
|
||||||
|
return PathBuf::from(dir).join("psysonic-cli-servers.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::env::temp_dir().join("psysonic-cli-servers.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli_search_response_path() -> PathBuf {
|
||||||
|
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
||||||
|
if !dir.is_empty() {
|
||||||
|
return PathBuf::from(dir).join("psysonic-cli-search.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::env::temp_dir().join("psysonic-cli-search.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Snapshot writer ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
pub fn write_cli_snapshot(payload: &Value) -> Result<(), String> {
|
||||||
|
let path = cli_snapshot_path();
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
let data = serde_json::to_string_pretty(payload).map_err(|e| e.to_string())?;
|
||||||
|
let tmp = path.with_extension("json.tmp");
|
||||||
|
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
||||||
|
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── List/search response IPC ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
pub(super) fn read_library_cli_response_blocking(max_wait: Duration) -> String {
|
||||||
|
let path = cli_library_response_path();
|
||||||
|
let deadline = Instant::now() + max_wait;
|
||||||
|
loop {
|
||||||
|
if let Ok(text) = std::fs::read_to_string(&path) {
|
||||||
|
let trimmed = text.trim();
|
||||||
|
if let Ok(v) = serde_json::from_str::<Value>(trimmed) {
|
||||||
|
if v.get("folders").and_then(|x| x.as_array()).is_some() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Instant::now() >= deadline {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(40));
|
||||||
|
}
|
||||||
|
std::fs::read_to_string(&path).unwrap_or_else(|_| "{}".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn print_library_cli_stdout(text: &str, json_out: bool) {
|
||||||
|
if json_out {
|
||||||
|
println!("{}", text.trim());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Ok(v) = serde_json::from_str::<Value>(text) {
|
||||||
|
print_library_human(&v);
|
||||||
|
} else {
|
||||||
|
println!("{}", text.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_library_cli_response(payload: &Value) -> Result<(), String> {
|
||||||
|
let path = cli_library_response_path();
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
let data = serde_json::to_string_pretty(payload).map_err(|e| e.to_string())?;
|
||||||
|
let tmp = path.with_extension("json.tmp");
|
||||||
|
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
||||||
|
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_server_list_cli_response(payload: &Value) -> Result<(), String> {
|
||||||
|
let path = cli_server_list_path();
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
let data = serde_json::to_string_pretty(payload).map_err(|e| e.to_string())?;
|
||||||
|
let tmp = path.with_extension("json.tmp");
|
||||||
|
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
||||||
|
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_search_cli_response(payload: &Value) -> Result<(), String> {
|
||||||
|
let path = cli_search_response_path();
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
let data = serde_json::to_string_pretty(payload).map_err(|e| e.to_string())?;
|
||||||
|
let tmp = path.with_extension("json.tmp");
|
||||||
|
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
||||||
|
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for `psysonic-cli-servers.json` after `cli:server-list`.
|
||||||
|
pub(super) fn read_server_list_cli_response_blocking(max_wait: Duration) -> String {
|
||||||
|
let path = cli_server_list_path();
|
||||||
|
let deadline = Instant::now() + max_wait;
|
||||||
|
loop {
|
||||||
|
if let Ok(text) = std::fs::read_to_string(&path) {
|
||||||
|
let trimmed = text.trim();
|
||||||
|
if let Ok(v) = serde_json::from_str::<Value>(trimmed) {
|
||||||
|
if v.get("servers").and_then(|x| x.as_array()).is_some() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Instant::now() >= deadline {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(40));
|
||||||
|
}
|
||||||
|
std::fs::read_to_string(&path).unwrap_or_else(|_| "{}".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn print_server_list_cli_stdout(text: &str, json_out: bool) {
|
||||||
|
if json_out {
|
||||||
|
println!("{}", text.trim());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Ok(v) = serde_json::from_str::<Value>(text) {
|
||||||
|
print_server_list_human(&v);
|
||||||
|
} else {
|
||||||
|
println!("{}", text.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for `psysonic-cli-search.json` after `cli:search`.
|
||||||
|
pub(super) fn read_search_cli_response_blocking(max_wait: Duration) -> String {
|
||||||
|
let path = cli_search_response_path();
|
||||||
|
let deadline = Instant::now() + max_wait;
|
||||||
|
loop {
|
||||||
|
if let Ok(text) = std::fs::read_to_string(&path) {
|
||||||
|
let trimmed = text.trim();
|
||||||
|
if let Ok(v) = serde_json::from_str::<Value>(trimmed) {
|
||||||
|
let ready = v.get("ready").and_then(|x| x.as_bool()) == Some(true);
|
||||||
|
let has_err = v.get("error").and_then(|x| x.as_str()).is_some_and(|s| !s.is_empty());
|
||||||
|
if ready || has_err {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Instant::now() >= deadline {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(40));
|
||||||
|
}
|
||||||
|
std::fs::read_to_string(&path).unwrap_or_else(|_| "{}".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn print_search_cli_stdout(text: &str, json_out: bool) {
|
||||||
|
if json_out {
|
||||||
|
println!("{}", text.trim());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Ok(v) = serde_json::from_str::<Value>(text) {
|
||||||
|
print_search_human(&v);
|
||||||
|
} else {
|
||||||
|
println!("{}", text.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Audio devices response writer ───────────────────────────────────────────
|
||||||
|
|
||||||
|
pub fn write_audio_device_cli_response(engine: &crate::audio::AudioEngine) -> Result<(), String> {
|
||||||
|
let devices = crate::audio::audio_list_devices_for_engine(engine);
|
||||||
|
let default_device = crate::audio::audio_default_output_device_name();
|
||||||
|
let selected = engine.selected_device.lock().unwrap().clone();
|
||||||
|
let v = serde_json::json!({
|
||||||
|
"devices": devices,
|
||||||
|
"default": default_device,
|
||||||
|
"selected": selected,
|
||||||
|
});
|
||||||
|
let path = cli_audio_device_response_path();
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
let data = serde_json::to_string_pretty(&v).map_err(|e| e.to_string())?;
|
||||||
|
let tmp = path.with_extension("json.tmp");
|
||||||
|
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
||||||
|
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+10
-204
@@ -1,20 +1,26 @@
|
|||||||
//! CLI surface for scripting / compositor bindings (e.g. Hyprland `exec`).
|
//! CLI surface for scripting / compositor bindings (e.g. Hyprland `exec`).
|
||||||
|
|
||||||
|
mod exchange;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod presenters;
|
mod presenters;
|
||||||
|
|
||||||
|
pub use exchange::*;
|
||||||
pub use parse::*;
|
pub use parse::*;
|
||||||
pub use presenters::print_audio_devices_human;
|
pub use presenters::print_audio_devices_human;
|
||||||
|
use exchange::{
|
||||||
|
print_library_cli_stdout, print_search_cli_stdout, print_server_list_cli_stdout,
|
||||||
|
read_library_cli_response_blocking, read_search_cli_response_blocking,
|
||||||
|
read_server_list_cli_response_blocking,
|
||||||
|
};
|
||||||
use parse::{cli_action_registry_entries, cli_registry_entry_by_command};
|
use parse::{cli_action_registry_entries, cli_registry_entry_by_command};
|
||||||
use presenters::{print_info_human, print_library_human, print_search_human, print_server_list_human};
|
use presenters::print_info_human;
|
||||||
|
|
||||||
// Bundled at compile time for `psysonic completions bash|zsh` (no extra files in packages).
|
// Bundled at compile time for `psysonic completions bash|zsh` (no extra files in packages).
|
||||||
const COMPLETIONS_BASH: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../completions/psysonic.bash"));
|
const COMPLETIONS_BASH: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../completions/psysonic.bash"));
|
||||||
const COMPLETIONS_ZSH: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../completions/_psysonic"));
|
const COMPLETIONS_ZSH: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../completions/_psysonic"));
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::Duration;
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque,
|
collections::VecDeque,
|
||||||
io::{BufRead, BufReader, Read, Seek, SeekFrom},
|
io::{BufRead, BufReader, Read, Seek, SeekFrom},
|
||||||
@@ -27,51 +33,6 @@ pub fn print_version() {
|
|||||||
println!("{}", env!("CARGO_PKG_VERSION"));
|
println!("{}", env!("CARGO_PKG_VERSION"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JSON snapshot path (written by the GUI process, read by `psysonic --info`).
|
|
||||||
pub fn cli_snapshot_path() -> PathBuf {
|
|
||||||
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
|
||||||
if !dir.is_empty() {
|
|
||||||
return PathBuf::from(dir).join("psysonic-cli-snapshot.json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::env::temp_dir().join("psysonic-cli-snapshot.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cli_audio_device_response_path() -> PathBuf {
|
|
||||||
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
|
||||||
if !dir.is_empty() {
|
|
||||||
return PathBuf::from(dir).join("psysonic-cli-audio-devices.json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::env::temp_dir().join("psysonic-cli-audio-devices.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cli_library_response_path() -> PathBuf {
|
|
||||||
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
|
||||||
if !dir.is_empty() {
|
|
||||||
return PathBuf::from(dir).join("psysonic-cli-library.json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::env::temp_dir().join("psysonic-cli-library.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cli_server_list_path() -> PathBuf {
|
|
||||||
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
|
||||||
if !dir.is_empty() {
|
|
||||||
return PathBuf::from(dir).join("psysonic-cli-servers.json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::env::temp_dir().join("psysonic-cli-servers.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cli_search_response_path() -> PathBuf {
|
|
||||||
if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
|
|
||||||
if !dir.is_empty() {
|
|
||||||
return PathBuf::from(dir).join("psysonic-cli-search.json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::env::temp_dir().join("psysonic-cli-search.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `psysonic completions …` — returns exit code when this argv should not start the GUI.
|
/// `psysonic completions …` — returns exit code when this argv should not start the GUI.
|
||||||
pub fn try_completions_dispatch(args: &[String]) -> Option<i32> {
|
pub fn try_completions_dispatch(args: &[String]) -> Option<i32> {
|
||||||
@@ -123,17 +84,6 @@ fn print_completions_install_help(program: &str) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_cli_snapshot(payload: &Value) -> Result<(), String> {
|
|
||||||
let path = cli_snapshot_path();
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
|
||||||
}
|
|
||||||
let data = serde_json::to_string_pretty(payload).map_err(|e| e.to_string())?;
|
|
||||||
let tmp = path.with_extension("json.tmp");
|
|
||||||
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
|
||||||
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_help(program: &str) {
|
pub fn print_help(program: &str) {
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
@@ -281,143 +231,18 @@ pub fn run_tail_and_exit(args: &[String]) -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the webview to write `psysonic-cli-library.json` after `cli:library-list`.
|
/// Wait for the webview to write `psysonic-cli-library.json` after `cli:library-list`.
|
||||||
fn read_library_cli_response_blocking(max_wait: Duration) -> String {
|
|
||||||
let path = cli_library_response_path();
|
|
||||||
let deadline = Instant::now() + max_wait;
|
|
||||||
loop {
|
|
||||||
if let Ok(text) = std::fs::read_to_string(&path) {
|
|
||||||
let trimmed = text.trim();
|
|
||||||
if let Ok(v) = serde_json::from_str::<Value>(trimmed) {
|
|
||||||
if v.get("folders").and_then(|x| x.as_array()).is_some() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if Instant::now() >= deadline {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
std::thread::sleep(Duration::from_millis(40));
|
|
||||||
}
|
|
||||||
std::fs::read_to_string(&path).unwrap_or_else(|_| "{}".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_library_cli_stdout(text: &str, json_out: bool) {
|
|
||||||
if json_out {
|
|
||||||
println!("{}", text.trim());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Ok(v) = serde_json::from_str::<Value>(text) {
|
|
||||||
print_library_human(&v);
|
|
||||||
} else {
|
|
||||||
println!("{}", text.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn write_library_cli_response(payload: &Value) -> Result<(), String> {
|
|
||||||
let path = cli_library_response_path();
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
|
||||||
}
|
|
||||||
let data = serde_json::to_string_pretty(payload).map_err(|e| e.to_string())?;
|
|
||||||
let tmp = path.with_extension("json.tmp");
|
|
||||||
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
|
||||||
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_server_list_cli_response(payload: &Value) -> Result<(), String> {
|
|
||||||
let path = cli_server_list_path();
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
|
||||||
}
|
|
||||||
let data = serde_json::to_string_pretty(payload).map_err(|e| e.to_string())?;
|
|
||||||
let tmp = path.with_extension("json.tmp");
|
|
||||||
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
|
||||||
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_search_cli_response(payload: &Value) -> Result<(), String> {
|
|
||||||
let path = cli_search_response_path();
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
|
||||||
}
|
|
||||||
let data = serde_json::to_string_pretty(payload).map_err(|e| e.to_string())?;
|
|
||||||
let tmp = path.with_extension("json.tmp");
|
|
||||||
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
|
||||||
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait for `psysonic-cli-servers.json` after `cli:server-list`.
|
/// Wait for `psysonic-cli-servers.json` after `cli:server-list`.
|
||||||
fn read_server_list_cli_response_blocking(max_wait: Duration) -> String {
|
|
||||||
let path = cli_server_list_path();
|
|
||||||
let deadline = Instant::now() + max_wait;
|
|
||||||
loop {
|
|
||||||
if let Ok(text) = std::fs::read_to_string(&path) {
|
|
||||||
let trimmed = text.trim();
|
|
||||||
if let Ok(v) = serde_json::from_str::<Value>(trimmed) {
|
|
||||||
if v.get("servers").and_then(|x| x.as_array()).is_some() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if Instant::now() >= deadline {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
std::thread::sleep(Duration::from_millis(40));
|
|
||||||
}
|
|
||||||
std::fs::read_to_string(&path).unwrap_or_else(|_| "{}".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_server_list_cli_stdout(text: &str, json_out: bool) {
|
|
||||||
if json_out {
|
|
||||||
println!("{}", text.trim());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Ok(v) = serde_json::from_str::<Value>(text) {
|
|
||||||
print_server_list_human(&v);
|
|
||||||
} else {
|
|
||||||
println!("{}", text.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Wait for `psysonic-cli-search.json` after `cli:search`.
|
/// Wait for `psysonic-cli-search.json` after `cli:search`.
|
||||||
fn read_search_cli_response_blocking(max_wait: Duration) -> String {
|
|
||||||
let path = cli_search_response_path();
|
|
||||||
let deadline = Instant::now() + max_wait;
|
|
||||||
loop {
|
|
||||||
if let Ok(text) = std::fs::read_to_string(&path) {
|
|
||||||
let trimmed = text.trim();
|
|
||||||
if let Ok(v) = serde_json::from_str::<Value>(trimmed) {
|
|
||||||
let ready = v.get("ready").and_then(|x| x.as_bool()) == Some(true);
|
|
||||||
let has_err = v.get("error").and_then(|x| x.as_str()).is_some_and(|s| !s.is_empty());
|
|
||||||
if ready || has_err {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if Instant::now() >= deadline {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
std::thread::sleep(Duration::from_millis(40));
|
|
||||||
}
|
|
||||||
std::fs::read_to_string(&path).unwrap_or_else(|_| "{}".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_search_cli_stdout(text: &str, json_out: bool) {
|
|
||||||
if json_out {
|
|
||||||
println!("{}", text.trim());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Ok(v) = serde_json::from_str::<Value>(text) {
|
|
||||||
print_search_human(&v);
|
|
||||||
} else {
|
|
||||||
println!("{}", text.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -523,25 +348,6 @@ pub fn run_info_and_exit(args: &[String]) -> ! {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub fn write_audio_device_cli_response(engine: &crate::audio::AudioEngine) -> Result<(), String> {
|
|
||||||
let devices = crate::audio::audio_list_devices_for_engine(engine);
|
|
||||||
let default_device = crate::audio::audio_default_output_device_name();
|
|
||||||
let selected = engine.selected_device.lock().unwrap().clone();
|
|
||||||
let v = serde_json::json!({
|
|
||||||
"devices": devices,
|
|
||||||
"default": default_device,
|
|
||||||
"selected": selected,
|
|
||||||
});
|
|
||||||
let path = cli_audio_device_response_path();
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
|
||||||
}
|
|
||||||
let data = serde_json::to_string_pretty(&v).map_err(|e| e.to_string())?;
|
|
||||||
let tmp = path.with_extension("json.tmp");
|
|
||||||
std::fs::write(&tmp, &data).map_err(|e| e.to_string())?;
|
|
||||||
std::fs::rename(&tmp, &path).map_err(|e| e.to_string())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Handle `--player` argv on the primary instance. Returns `true` if argv was a CLI action
|
/// Handle `--player` argv on the primary instance. Returns `true` if argv was a CLI action
|
||||||
|
|||||||
Reference in New Issue
Block a user