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;