mineral-server 0.5.10

Mineral 后台 server:收纳 audio engine + task scheduler + channels,对外发 ClientHandle
//! 渲染 daemon 配置校验摘要;不执行 setup。

use super::{BackendKind, DaemonConfig};
use mineral_config::ConfigWarning;
use mineral_model::BitRate;
use std::path::Path;

/// 渲染有效配置摘要与诊断类别,不展示底层错误文本。
///
/// # Params:
///   - `config`: 已加载(可能回落默认)的配置
///   - `warnings`: daemon 配置读取、求值或落型问题
///   - `default_download_dir`: `download.dir` 缺省时的真实回落目录(调用方解析好传入,
///     保持本函数纯净/快照确定;诊断据此把「默认」展示成真实路径而非占位)
///   - `color`: 是否输出 ANSI 颜色(非 tty / 测试传 `false`)
///
/// # Return:
///   多行诊断文本(无尾换行)
pub fn render_check(
    config: &DaemonConfig,
    warnings: &[ConfigWarning],
    default_download_dir: &Path,
    color: bool,
) -> String {
    let mut lines = Vec::<String>::new();
    lines.push(paint("Mineral daemon configuration check", "1;36", color));
    lines.push(format!("  volume: {}", config.audio().volume()));
    lines.push(format!(
        "  audio backend: {}",
        backend_name(config.audio().backend())
    ));
    lines.push(format!(
        "  audio cache (bytes): {}",
        config.cache().audio_capacity()
    ));
    lines.push(format!(
        "  download quality: {}",
        quality_name(config.download().quality())
    ));
    lines.push(format!(
        "  download dir: {}",
        dir_label(config, default_download_dir)
    ));
    lines.push(format!(
        "  netease timeout (s): {}",
        config.sources().netease().timeout_secs()
    ));
    lines.push(format!(
        "  gapless prefetch (ms): {}",
        config.gapless_prefetch_ms()
    ));

    append_warnings(&mut lines, warnings, color);
    lines.join("\n")
}

/// 下载目录标签:显式配置则显示该路径;`None` 则显示真实默认目录 + `(默认)` 标注。
///
/// # Params:
///   - `config`: 配置
///   - `default_download_dir`: `download.dir` 缺省时的真实回落目录
///
/// # Return:
///   下载目录的人类可读路径
fn dir_label(config: &DaemonConfig, default_download_dir: &Path) -> String {
    match config.download().dir() {
        Some(path) => path.display().to_string(),
        None => format!("{} (default)", default_download_dir.display()),
    }
}

/// 后端枚举 → 展示名。
///
/// # Params:
///   - `backend`: 后端选择
///
/// # Return:
///   展示名
fn backend_name(backend: &BackendKind) -> &'static str {
    match backend {
        BackendKind::Auto => "auto",
        BackendKind::Null => "null",
    }
}

/// 音质枚举 → 展示名(小写,与 serde / config 字符串一致)。
///
/// # Params:
///   - `quality`: 音质
///
/// # Return:
///   展示名
fn quality_name(quality: &BitRate) -> &'static str {
    match quality {
        BitRate::Standard => "standard",
        BitRate::Higher => "higher",
        BitRate::Exhigh => "exhigh",
        BitRate::Lossless => "lossless",
        BitRate::Hires => "hires",
    }
}

/// 追加结构化诊断的摘要;底层错误保留在日志的 source 链中。
fn append_warnings(lines: &mut Vec<String>, warnings: &[ConfigWarning], color: bool) {
    if warnings.is_empty() {
        lines.push(paint("Config OK, no warnings.", "32", color));
    } else {
        lines.push(paint(
            &format!("{} warning(s):", warnings.len()),
            "1;31",
            color,
        ));
        for warning in warnings {
            lines.push(paint(&format!("  - {warning}"), "31", color));
        }
    }
}

/// 按需给文本加 ANSI 颜色;`color = false` 时原样返回。
///
/// # Params:
///   - `text`: 文本
///   - `code`: ANSI SGR 参数(如 `"32"`)
///   - `color`: 是否上色
///
/// # Return:
///   带 / 不带 ANSI 的文本
fn paint(text: &str, code: &str, color: bool) -> String {
    if color {
        format!("\x1b[{code}m{text}\x1b[0m")
    } else {
        text.to_owned()
    }
}