profiles.rs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. use super::{prfitem::PrfItem, ChainItem};
  2. use crate::utils::{dirs, help};
  3. use anyhow::{bail, Context, Result};
  4. use serde::{Deserialize, Serialize};
  5. use serde_yaml::Mapping;
  6. use std::{fs, io::Write};
  7. /// Define the `profiles.yaml` schema
  8. #[derive(Default, Debug, Clone, Deserialize, Serialize)]
  9. pub struct IProfiles {
  10. /// same as PrfConfig.current
  11. pub current: Option<String>,
  12. /// same as PrfConfig.chain
  13. pub chain: Option<Vec<String>>,
  14. /// record valid fields for clash
  15. pub valid: Option<Vec<String>>,
  16. /// profile list
  17. pub items: Option<Vec<PrfItem>>,
  18. }
  19. macro_rules! patch {
  20. ($lv: expr, $rv: expr, $key: tt) => {
  21. if ($rv.$key).is_some() {
  22. $lv.$key = $rv.$key;
  23. }
  24. };
  25. }
  26. impl IProfiles {
  27. pub fn new() -> Self {
  28. match dirs::profiles_path().and_then(|path| help::read_yaml::<Self>(&path)) {
  29. Ok(mut profiles) => {
  30. if profiles.items.is_none() {
  31. profiles.items = Some(vec![]);
  32. }
  33. // compatible with the old old old version
  34. profiles.items.as_mut().map(|items| {
  35. for mut item in items.iter_mut() {
  36. if item.uid.is_none() {
  37. item.uid = Some(help::get_uid("d"));
  38. }
  39. }
  40. });
  41. profiles
  42. }
  43. Err(err) => {
  44. log::error!(target: "app", "{err}");
  45. Self::template()
  46. }
  47. }
  48. }
  49. pub fn template() -> Self {
  50. Self {
  51. valid: Some(vec!["dns".into()]),
  52. items: Some(vec![]),
  53. ..Self::default()
  54. }
  55. }
  56. pub fn save_file(&self) -> Result<()> {
  57. help::save_yaml(
  58. &dirs::profiles_path()?,
  59. self,
  60. Some("# Profiles Config for Clash Verge"),
  61. )
  62. }
  63. /// 只修改current,valid和chain
  64. pub fn patch_config(&mut self, patch: IProfiles) -> Result<()> {
  65. if self.items.is_none() {
  66. self.items = Some(vec![]);
  67. }
  68. if let Some(current) = patch.current {
  69. let items = self.items.as_ref().unwrap();
  70. let some_uid = Some(current);
  71. if items.iter().any(|e| e.uid == some_uid) {
  72. self.current = some_uid;
  73. }
  74. }
  75. if let Some(chain) = patch.chain {
  76. self.chain = Some(chain);
  77. }
  78. if let Some(valid) = patch.valid {
  79. self.valid = Some(valid);
  80. }
  81. Ok(())
  82. }
  83. pub fn get_current(&self) -> Option<String> {
  84. self.current.clone()
  85. }
  86. /// get items ref
  87. pub fn get_items(&self) -> Option<&Vec<PrfItem>> {
  88. self.items.as_ref()
  89. }
  90. /// find the item by the uid
  91. pub fn get_item(&self, uid: &String) -> Result<&PrfItem> {
  92. if let Some(items) = self.items.as_ref() {
  93. let some_uid = Some(uid.clone());
  94. for each in items.iter() {
  95. if each.uid == some_uid {
  96. return Ok(each);
  97. }
  98. }
  99. }
  100. bail!("failed to get the profile item \"uid:{uid}\"");
  101. }
  102. /// append new item
  103. /// if the file_data is some
  104. /// then should save the data to file
  105. pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> {
  106. if item.uid.is_none() {
  107. bail!("the uid should not be null");
  108. }
  109. // save the file data
  110. // move the field value after save
  111. if let Some(file_data) = item.file_data.take() {
  112. if item.file.is_none() {
  113. bail!("the file should not be null");
  114. }
  115. let file = item.file.clone().unwrap();
  116. let path = dirs::app_profiles_dir()?.join(&file);
  117. fs::File::create(path)
  118. .context(format!("failed to create file \"{}\"", file))?
  119. .write(file_data.as_bytes())
  120. .context(format!("failed to write to file \"{}\"", file))?;
  121. }
  122. if self.items.is_none() {
  123. self.items = Some(vec![]);
  124. }
  125. self.items.as_mut().map(|items| items.push(item));
  126. self.save_file()
  127. }
  128. /// update the item value
  129. pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
  130. let mut items = self.items.take().unwrap_or(vec![]);
  131. for mut each in items.iter_mut() {
  132. if each.uid == Some(uid.clone()) {
  133. patch!(each, item, itype);
  134. patch!(each, item, name);
  135. patch!(each, item, desc);
  136. patch!(each, item, file);
  137. patch!(each, item, url);
  138. patch!(each, item, selected);
  139. patch!(each, item, extra);
  140. patch!(each, item, updated);
  141. patch!(each, item, option);
  142. self.items = Some(items);
  143. return self.save_file();
  144. }
  145. }
  146. self.items = Some(items);
  147. bail!("failed to find the profile item \"uid:{uid}\"")
  148. }
  149. /// be used to update the remote item
  150. /// only patch `updated` `extra` `file_data`
  151. pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> {
  152. if self.items.is_none() {
  153. self.items = Some(vec![]);
  154. }
  155. // find the item
  156. let _ = self.get_item(&uid)?;
  157. if let Some(items) = self.items.as_mut() {
  158. let some_uid = Some(uid.clone());
  159. for mut each in items.iter_mut() {
  160. if each.uid == some_uid {
  161. each.extra = item.extra;
  162. each.updated = item.updated;
  163. // save the file data
  164. // move the field value after save
  165. if let Some(file_data) = item.file_data.take() {
  166. let file = each.file.take();
  167. let file =
  168. file.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid)));
  169. // the file must exists
  170. each.file = Some(file.clone());
  171. let path = dirs::app_profiles_dir()?.join(&file);
  172. fs::File::create(path)
  173. .context(format!("failed to create file \"{}\"", file))?
  174. .write(file_data.as_bytes())
  175. .context(format!("failed to write to file \"{}\"", file))?;
  176. }
  177. break;
  178. }
  179. }
  180. }
  181. self.save_file()
  182. }
  183. /// delete item
  184. /// if delete the current then return true
  185. pub fn delete_item(&mut self, uid: String) -> Result<bool> {
  186. let current = self.current.as_ref().unwrap_or(&uid);
  187. let current = current.clone();
  188. let mut items = self.items.take().unwrap_or(vec![]);
  189. let mut index = None;
  190. // get the index
  191. for i in 0..items.len() {
  192. if items[i].uid == Some(uid.clone()) {
  193. index = Some(i);
  194. break;
  195. }
  196. }
  197. if let Some(index) = index {
  198. items.remove(index).file.map(|file| {
  199. let _ = dirs::app_profiles_dir().map(|path| {
  200. let path = path.join(file);
  201. if path.exists() {
  202. let _ = fs::remove_file(path);
  203. }
  204. });
  205. });
  206. }
  207. // delete the original uid
  208. if current == uid {
  209. self.current = match items.len() > 0 {
  210. true => items[0].uid.clone(),
  211. false => None,
  212. };
  213. }
  214. self.items = Some(items);
  215. self.save_file()?;
  216. Ok(current == uid)
  217. }
  218. /// generate the current Mapping data
  219. fn gen_current(&self) -> Result<Mapping> {
  220. let config = Mapping::new();
  221. if self.current.is_none() || self.items.is_none() {
  222. return Ok(config);
  223. }
  224. let current = self.current.as_ref().unwrap();
  225. for item in self.items.as_ref().unwrap().iter() {
  226. if item.uid.as_ref() == Some(current) {
  227. let file_path = match item.file.as_ref() {
  228. Some(file) => dirs::app_profiles_dir()?.join(file),
  229. None => bail!("failed to get the file field"),
  230. };
  231. return Ok(help::read_merge_mapping(&file_path)?);
  232. }
  233. }
  234. bail!("failed to find the current profile \"uid:{current}\"");
  235. }
  236. /// generate the data for activate clash config
  237. pub fn gen_activate(&self) -> Result<PrfActivate> {
  238. let current = self.gen_current()?;
  239. let chain = match self.chain.as_ref() {
  240. Some(chain) => chain
  241. .iter()
  242. .filter_map(|uid| self.get_item(uid).ok())
  243. .filter_map(|item| item.to_enhance())
  244. .collect::<Vec<ChainItem>>(),
  245. None => vec![],
  246. };
  247. let valid = self.valid.clone().unwrap_or(vec![]);
  248. Ok(PrfActivate {
  249. current,
  250. chain,
  251. valid,
  252. })
  253. }
  254. }
  255. #[derive(Default, Clone)]
  256. pub struct PrfActivate {
  257. pub current: Mapping,
  258. pub chain: Vec<ChainItem>,
  259. pub valid: Vec<String>,
  260. }