313 lines
9.1 KiB
Rust
Raw Normal View History

2022-11-17 17:07:13 +08:00
use super::{prfitem::PrfItem, ChainItem};
2022-11-18 18:18:41 +08:00
use crate::utils::{dirs, help};
2022-11-14 01:26:33 +08:00
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use serde_yaml::Mapping;
use std::{fs, io::Write};
/// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct IProfiles {
/// same as PrfConfig.current
2022-11-17 17:07:13 +08:00
pub current: Option<String>,
2022-11-14 01:26:33 +08:00
/// same as PrfConfig.chain
2022-11-17 17:07:13 +08:00
pub chain: Option<Vec<String>>,
2022-11-14 01:26:33 +08:00
/// record valid fields for clash
2022-11-17 17:07:13 +08:00
pub valid: Option<Vec<String>>,
2022-11-14 01:26:33 +08:00
/// profile list
2022-11-17 17:07:13 +08:00
pub items: Option<Vec<PrfItem>>,
2022-11-14 01:26:33 +08:00
}
macro_rules! patch {
($lv: expr, $rv: expr, $key: tt) => {
if ($rv.$key).is_some() {
$lv.$key = $rv.$key;
}
};
}
impl IProfiles {
2022-11-16 01:26:41 +08:00
pub fn new() -> Self {
2022-11-18 18:18:41 +08:00
match dirs::profiles_path().and_then(|path| help::read_yaml::<Self>(&path)) {
Ok(mut profiles) => {
if profiles.items.is_none() {
profiles.items = Some(vec![]);
}
// compatible with the old old old version
profiles.items.as_mut().map(|items| {
for mut item in items.iter_mut() {
if item.uid.is_none() {
item.uid = Some(help::get_uid("d"));
}
}
});
profiles
}
Err(err) => {
log::error!(target: "app", "{err}");
Self::template()
}
}
2022-11-16 01:26:41 +08:00
}
pub fn template() -> Self {
Self {
valid: Some(vec!["dns".into()]),
items: Some(vec![]),
..Self::default()
2022-11-14 01:26:33 +08:00
}
}
pub fn save_file(&self) -> Result<()> {
2022-11-18 18:18:41 +08:00
help::save_yaml(
&dirs::profiles_path()?,
2022-11-14 01:26:33 +08:00
self,
2022-11-18 18:18:41 +08:00
Some("# Profiles Config for Clash Verge"),
2022-11-14 01:26:33 +08:00
)
}
2022-11-18 18:18:41 +08:00
/// 只修改currentvalid和chain
pub fn patch_config(&mut self, patch: IProfiles) -> Result<()> {
if self.items.is_none() {
self.items = Some(vec![]);
}
if let Some(current) = patch.current {
let items = self.items.as_ref().unwrap();
let some_uid = Some(current);
if items.iter().any(|e| e.uid == some_uid) {
self.current = some_uid;
}
}
if let Some(chain) = patch.chain {
self.chain = Some(chain);
}
if let Some(valid) = patch.valid {
self.valid = Some(valid);
}
Ok(())
}
2022-11-14 01:26:33 +08:00
pub fn get_current(&self) -> Option<String> {
self.current.clone()
}
/// get items ref
pub fn get_items(&self) -> Option<&Vec<PrfItem>> {
self.items.as_ref()
}
/// find the item by the uid
pub fn get_item(&self, uid: &String) -> Result<&PrfItem> {
2022-11-17 23:21:13 +08:00
if let Some(items) = self.items.as_ref() {
2022-11-14 01:26:33 +08:00
let some_uid = Some(uid.clone());
for each in items.iter() {
if each.uid == some_uid {
return Ok(each);
}
}
}
bail!("failed to get the profile item \"uid:{uid}\"");
}
/// append new item
/// if the file_data is some
/// then should save the data to file
pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> {
if item.uid.is_none() {
bail!("the uid should not be null");
}
// save the file data
// move the field value after save
if let Some(file_data) = item.file_data.take() {
if item.file.is_none() {
bail!("the file should not be null");
}
let file = item.file.clone().unwrap();
let path = dirs::app_profiles_dir()?.join(&file);
2022-11-14 01:26:33 +08:00
fs::File::create(path)
.context(format!("failed to create file \"{}\"", file))?
.write(file_data.as_bytes())
.context(format!("failed to write to file \"{}\"", file))?;
}
if self.items.is_none() {
self.items = Some(vec![]);
}
self.items.as_mut().map(|items| items.push(item));
self.save_file()
}
/// update the item value
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
let mut items = self.items.take().unwrap_or(vec![]);
for mut each in items.iter_mut() {
if each.uid == Some(uid.clone()) {
patch!(each, item, itype);
patch!(each, item, name);
patch!(each, item, desc);
patch!(each, item, file);
patch!(each, item, url);
patch!(each, item, selected);
patch!(each, item, extra);
patch!(each, item, updated);
patch!(each, item, option);
self.items = Some(items);
return self.save_file();
}
}
self.items = Some(items);
bail!("failed to find the profile item \"uid:{uid}\"")
}
/// be used to update the remote item
/// only patch `updated` `extra` `file_data`
pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> {
if self.items.is_none() {
self.items = Some(vec![]);
}
// find the item
let _ = self.get_item(&uid)?;
if let Some(items) = self.items.as_mut() {
let some_uid = Some(uid.clone());
for mut each in items.iter_mut() {
if each.uid == some_uid {
each.extra = item.extra;
each.updated = item.updated;
// save the file data
// move the field value after save
if let Some(file_data) = item.file_data.take() {
let file = each.file.take();
let file =
file.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid)));
// the file must exists
each.file = Some(file.clone());
let path = dirs::app_profiles_dir()?.join(&file);
2022-11-14 01:26:33 +08:00
fs::File::create(path)
.context(format!("failed to create file \"{}\"", file))?
.write(file_data.as_bytes())
.context(format!("failed to write to file \"{}\"", file))?;
}
break;
}
}
}
self.save_file()
}
/// delete item
/// if delete the current then return true
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
let current = self.current.as_ref().unwrap_or(&uid);
let current = current.clone();
let mut items = self.items.take().unwrap_or(vec![]);
let mut index = None;
// get the index
for i in 0..items.len() {
if items[i].uid == Some(uid.clone()) {
index = Some(i);
break;
}
}
if let Some(index) = index {
items.remove(index).file.map(|file| {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
2022-11-14 01:26:33 +08:00
});
}
// delete the original uid
if current == uid {
self.current = match items.len() > 0 {
true => items[0].uid.clone(),
false => None,
};
}
self.items = Some(items);
self.save_file()?;
Ok(current == uid)
}
/// generate the current Mapping data
fn gen_current(&self) -> Result<Mapping> {
let config = Mapping::new();
if self.current.is_none() || self.items.is_none() {
return Ok(config);
}
let current = self.current.as_ref().unwrap();
2022-11-14 01:26:33 +08:00
for item in self.items.as_ref().unwrap().iter() {
if item.uid.as_ref() == Some(current) {
let file_path = match item.file.as_ref() {
Some(file) => dirs::app_profiles_dir()?.join(file),
2022-11-14 01:26:33 +08:00
None => bail!("failed to get the file field"),
};
2022-11-18 18:18:41 +08:00
return Ok(help::read_merge_mapping(&file_path)?);
2022-11-14 01:26:33 +08:00
}
}
bail!("failed to find the current profile \"uid:{current}\"");
2022-11-14 01:26:33 +08:00
}
/// generate the data for activate clash config
pub fn gen_activate(&self) -> Result<PrfActivate> {
let current = self.gen_current()?;
let chain = match self.chain.as_ref() {
Some(chain) => chain
.iter()
.filter_map(|uid| self.get_item(uid).ok())
.filter_map(|item| item.to_enhance())
.collect::<Vec<ChainItem>>(),
None => vec![],
};
let valid = self.valid.clone().unwrap_or(vec![]);
Ok(PrfActivate {
current,
chain,
valid,
})
}
}
#[derive(Default, Clone)]
pub struct PrfActivate {
pub current: Mapping,
pub chain: Vec<ChainItem>,
pub valid: Vec<String>,
}