use std::{collections::HashSet, process};
use crate::{
cargo::CargoConfig,
constants::{APP_NAME, APP_VERSION, CARGO, RUST_LANG},
runtime::RuntimeConfig,
utils::{
append_end_spaces, exec_command, is_registry_addr, is_registry_dl, is_registry_name,
network_delay, status_prefix, to_out,
},
};
pub struct Registry {
rc: RuntimeConfig,
cargo: CargoConfig,
}
impl Registry {
pub fn new() -> Self {
Registry {
rc: RuntimeConfig::new(),
cargo: CargoConfig::new(),
}
}
pub fn select(&mut self, name: Option<&String>) {
let name = is_registry_name(name).trim();
let remaining_registries = self.rc.to_tuples(None);
if let Err(name) = self
.cargo
.use_registry(name, self.rc.get(name), remaining_registries)
{
let keys = self.rc.to_key_string();
if keys.is_empty() {
return to_out(format!(
"没有找到 {} 镜像,配置中的镜像列表为空,请用 \"crm save\" 添加镜像后重试",
name,
));
}
to_out(format!("没有找到 {} 镜像,可选的镜像是:\n{}", name, keys));
};
self.cargo.make();
}
pub fn remove(&mut self, name: Option<&String>) {
let name = is_registry_name(name).trim();
if self.rc.get_default(name).is_some() {
to_out("请不要删除内置镜像");
process::exit(11);
}
if self.rc.get_extend(name).is_none() {
to_out(format!("删除失败,{} 镜像不存在", name));
process::exit(12);
}
self.rc.remove(name);
self.rc.write();
}
pub fn save(&mut self, name: Option<&String>, addr: Option<&String>, dl: Option<&String>) {
let name = is_registry_name(name).trim();
let addr = is_registry_addr(addr).trim();
let dl = is_registry_dl(dl).trim();
self.rc.save(name, addr, dl);
self.rc.write();
}
pub fn list(&self, current: &String) -> String {
self.rc.to_string(current, Some("- "))
}
pub fn current(&self) -> (String, Option<String>) {
let (name, addr) = CargoConfig::new().current();
let addr = match addr {
Some(addr) => Some(addr),
None => self.rc.get(&name).map(|addr| addr.registry.clone()),
};
(name, addr)
}
pub fn default(&mut self) {
self.select(Some(&RUST_LANG.to_string()));
}
fn delay_tester<'a, I, P, F>(
&self,
iter: I,
predicate: P,
conversion_url: F,
) -> Vec<(String, Option<u128>)>
where
I: Iterator<Item = &'a String>,
P: FnMut(&&'a String) -> bool,
F: FnMut(&'a String) -> (String, Option<String>),
{
let urls: Vec<(String, Option<String>)> =
iter.filter(predicate).map(conversion_url).collect();
network_delay(urls, Some(1), true)
}
pub fn best(&mut self, mode: Option<&String>) {
let names = self.rc.registry_names();
let iter = names.iter();
let dld = ["sjtu", "ustc", "rsproxy"].iter().map(ToString::to_string);
const SPARSE: &str = "-sparse";
let tested = match mode {
Some(mode) => match mode.to_lowercase().as_str() {
"sparse" => self.delay_tester(
iter,
|name| name.ends_with(SPARSE),
|name| (name.to_string(), self.to_connected_url(name)),
),
"git" => self.delay_tester(
iter,
|name| !name.ends_with(SPARSE),
|name| (name.to_string(), self.to_download_url(name)),
),
"git-download" => {
let s = dld.collect::<HashSet<String>>();
self.delay_tester(
iter,
|name| s.contains(&**name),
|name| (name.to_string(), self.to_download_url(name)),
)
}
"sparse-download" => {
let s = dld.map(|v| v + SPARSE).collect::<HashSet<String>>();
self.delay_tester(
iter,
|name| s.contains(&**name),
|name| (name.to_string(), self.to_connected_url(name)),
)
}
_ => {
to_out("参数错误,您不能使用除 \"sparse\"、\"git\"、\"git-download\" 或 \"sparse-download\" 之外的值");
process::exit(19)
}
},
None => self.test_download_status(None, Some(1)),
};
let found = tested.iter().find(|v| v.1.is_some());
if found.is_none() {
return to_out("没有可切换的镜像源");
}
let registry_name = &found.unwrap().0;
self.select(Some(registry_name));
to_out(format!("已切换到 {} 镜像源", registry_name));
}
fn to_download_url(&self, name: &str) -> Option<String> {
match self.rc.get(name) {
Some(rd) => {
let dl = rd.dl.clone();
let url = if !dl.ends_with("/api/v1/crates") {
dl.replace("{crate}", APP_NAME)
.replace("{version}", APP_VERSION)
} else {
format!("{}/{}/{}/download", dl, APP_NAME, APP_VERSION)
};
Some(url)
}
None => None,
}
}
fn test_download_status(
&self,
name: Option<&String>,
sender_size: Option<usize>,
) -> Vec<(String, Option<u128>)> {
let urls = match name {
Some(name) => {
if self.rc.get(name).is_none() {
to_out(format!("测试失败,{} 镜像不存在", name));
process::exit(13);
}
vec![(name.to_string(), self.to_download_url(name))]
}
None => self
.rc
.registry_names()
.iter()
.filter(|name| !name.ends_with("-sparse"))
.map(|name| (name.to_string(), self.to_download_url(name)))
.collect(),
};
network_delay(urls, sender_size, false)
}
fn to_connected_url(&self, name: &str) -> Option<String> {
match self.rc.get(name) {
Some(rd) => {
let registry = rd.registry.clone();
if registry.starts_with("git:") {
return None;
}
Some(registry.replace("sparse+", ""))
}
None => None,
}
}
fn test_connected_status(
&self,
name: Option<&String>,
sender_size: Option<usize>,
) -> Vec<(String, Option<u128>)> {
let urls = match name {
Some(name) => {
if self.rc.get(name).is_none() {
to_out(format!("测试失败,{} 镜像不存在", name));
process::exit(13);
}
vec![(name.to_string(), self.to_connected_url(name))]
}
None => self
.rc
.registry_names()
.iter()
.map(|name| (name.to_string(), self.to_connected_url(name)))
.collect(),
};
network_delay(urls, sender_size, true)
}
pub fn test(&self, current: &String, name: Option<&String>) {
let connected_status: Vec<String> = self
.test_connected_status(name, None)
.iter()
.map(|(name, status)| {
let prefix = status_prefix(name, current);
let name = append_end_spaces(name, None);
let status = match status {
Some(s) => format!("{} ms", s),
None => "failed".to_string(),
};
format!("{}{} -- {}", prefix, name, status)
})
.collect();
println!("网络连接延迟:\n{}\n", connected_status.join("\n"));
let download_status: Vec<String> = self
.test_download_status(name, None)
.iter()
.map(|(name, status)| {
let prefix = status_prefix(name, current);
let name = append_end_spaces(name, None);
let status = match status {
Some(s) => format!("{} ms", s),
None => "failed".to_string(),
};
format!("{}{} -- {}", prefix, name, status)
})
.collect();
println!("软件包下载延迟:\n{}", download_status.join("\n"));
}
fn exec(&mut self, command: &str) {
let (registry_name, _) = self.current();
let is_default_registry = registry_name.eq(RUST_LANG);
if !is_default_registry {
self.default();
}
if let Err(e) = exec_command(command, None) {
to_out(e);
};
if !is_default_registry {
self.select(Some(®istry_name));
}
}
pub fn publish(&mut self, args: String) {
self.exec(&format!("{} publish {}", CARGO, args.trim()));
}
pub fn update(&mut self, args: String) {
self.exec(&format!("{} update {}", CARGO, args.trim()));
}
pub fn install(&mut self, args: String) {
let args = args.trim();
let args = if args.is_empty() { "--help" } else { args };
self.exec(&format!("{} install {}", CARGO, args));
}
}
impl Default for Registry {
fn default() -> Self {
Self::new()
}
}