Skip to main content

futucli/cmd/
lang_pack.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::{Result, anyhow};
4use futu_core::localization::{
5    LanguagePackInstallOptions, LanguagePackRemoteUpdateOptions, default_language_pack_cache_root,
6    import_language_pack_dir, language_pack_status, record_language_pack_update_error,
7    update_language_pack_from_endpoint,
8};
9use serde::Serialize;
10use tabled::Tabled;
11
12use crate::cli::LanguagePackCommand;
13use crate::output::OutputFormat;
14
15#[cfg(test)]
16use futu_core::localization::{
17    ensure_allowed_language_pack_remote_scheme, language_pack_manifest_url,
18    language_pack_sha256_hex,
19};
20#[cfg(test)]
21use reqwest::Url;
22
23#[derive(Debug, Serialize, Tabled)]
24struct StatusRow {
25    cache_root: String,
26    runtime_source: String,
27    pack_version: String,
28    source: String,
29    profile: String,
30    entries: usize,
31    last_error: String,
32}
33
34#[derive(Debug, Serialize, Tabled)]
35struct ImportRow {
36    cache_root: String,
37    pack_version: String,
38    source: String,
39    entries: usize,
40}
41
42pub async fn run(command: LanguagePackCommand, output: OutputFormat) -> Result<()> {
43    match command {
44        LanguagePackCommand::Status { cache_dir } => run_status(cache_dir.as_deref(), output),
45        LanguagePackCommand::Import {
46            dir,
47            cache_dir,
48            pack_version,
49            source,
50            profile,
51        } => run_import(&dir, cache_dir, pack_version, source, profile, output),
52        LanguagePackCommand::Update {
53            endpoint,
54            cache_dir,
55            timeout_ms,
56            lang,
57        } => run_update(&endpoint, cache_dir, timeout_ms, lang.as_deref(), output).await,
58    }
59}
60
61fn run_status(cache_dir: Option<&Path>, output: OutputFormat) -> Result<()> {
62    let status = language_pack_status(cache_dir);
63    let row = StatusRow {
64        cache_root: status.cache_root,
65        runtime_source: status.runtime_source.to_string(),
66        pack_version: status.pack_version.unwrap_or_else(|| "-".to_string()),
67        source: status.source.unwrap_or_else(|| "-".to_string()),
68        profile: status.profile.unwrap_or_else(|| "-".to_string()),
69        entries: status.entries.len(),
70        last_error: status.last_error.unwrap_or_else(|| "-".to_string()),
71    };
72    output.print_rows(std::slice::from_ref(&row), std::slice::from_ref(&row))?;
73    Ok(())
74}
75
76fn run_import(
77    dir: &Path,
78    cache_dir: Option<PathBuf>,
79    pack_version: String,
80    source: String,
81    profile: Option<String>,
82    output: OutputFormat,
83) -> Result<()> {
84    let cache_root = cache_dir.unwrap_or_else(default_language_pack_cache_root);
85    let manifest = import_language_pack_dir(
86        dir,
87        LanguagePackInstallOptions {
88            cache_root: cache_root.clone(),
89            pack_version,
90            source,
91            profile,
92        },
93    )
94    .map_err(|error| anyhow!(error))?;
95    let row = ImportRow {
96        cache_root: cache_root.display().to_string(),
97        pack_version: manifest.pack_version,
98        source: manifest.source,
99        entries: manifest.entries.len(),
100    };
101    output.print_rows(std::slice::from_ref(&row), std::slice::from_ref(&row))?;
102    Ok(())
103}
104
105async fn run_update(
106    endpoint: &str,
107    cache_dir: Option<PathBuf>,
108    timeout_ms: u64,
109    lang_filter: Option<&str>,
110    output: OutputFormat,
111) -> Result<()> {
112    let cache_root = cache_dir.unwrap_or_else(default_language_pack_cache_root);
113    match run_update_inner(
114        endpoint,
115        cache_root.clone(),
116        timeout_ms,
117        lang_filter,
118        output,
119    )
120    .await
121    {
122        Ok(()) => Ok(()),
123        Err(error) => {
124            let _ = record_language_pack_update_error(&cache_root, &error.to_string());
125            Err(error)
126        }
127    }
128}
129
130async fn run_update_inner(
131    endpoint: &str,
132    cache_root: PathBuf,
133    timeout_ms: u64,
134    lang_filter: Option<&str>,
135    output: OutputFormat,
136) -> Result<()> {
137    let installed = update_language_pack_from_endpoint(LanguagePackRemoteUpdateOptions {
138        endpoint: endpoint.to_string(),
139        cache_root: cache_root.clone(),
140        timeout_ms,
141        lang_filter: lang_filter.map(ToOwned::to_owned),
142    })
143    .await
144    .map_err(|error| anyhow!(error))?;
145    let row = ImportRow {
146        cache_root: cache_root.display().to_string(),
147        pack_version: installed.pack_version,
148        source: installed.source,
149        entries: installed.entries.len(),
150    };
151    output.print_rows(std::slice::from_ref(&row), std::slice::from_ref(&row))?;
152    Ok(())
153}
154
155#[cfg(test)]
156fn manifest_url(endpoint: &str) -> Result<Url> {
157    language_pack_manifest_url(endpoint).map_err(anyhow::Error::msg)
158}
159
160#[cfg(test)]
161fn ensure_allowed_remote_scheme(url: &Url) -> Result<()> {
162    ensure_allowed_language_pack_remote_scheme(url).map_err(anyhow::Error::msg)
163}
164
165#[cfg(test)]
166mod tests;