profiles.rs 7.9 KB

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