profiles.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. use super::{prfitem::PrfItem, PrfOption};
  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. /// profile list
  13. pub items: Option<Vec<PrfItem>>,
  14. }
  15. macro_rules! patch {
  16. ($lv: expr, $rv: expr, $key: tt) => {
  17. if ($rv.$key).is_some() {
  18. $lv.$key = $rv.$key;
  19. }
  20. };
  21. }
  22. impl IProfiles {
  23. pub fn new() -> Self {
  24. match dirs::profiles_path().and_then(|path| help::read_yaml::<Self>(&path)) {
  25. Ok(mut profiles) => {
  26. if profiles.items.is_none() {
  27. profiles.items = Some(vec![]);
  28. }
  29. // compatible with the old old old version
  30. if let Some(items) = profiles.items.as_mut() {
  31. for item in items.iter_mut() {
  32. if item.uid.is_none() {
  33. item.uid = Some(help::get_uid("d"));
  34. }
  35. }
  36. }
  37. profiles
  38. }
  39. Err(err) => {
  40. log::error!(target: "app", "{err}");
  41. Self::template()
  42. }
  43. }
  44. }
  45. pub fn template() -> Self {
  46. Self {
  47. items: Some(vec![]),
  48. ..Self::default()
  49. }
  50. }
  51. pub fn save_file(&self) -> Result<()> {
  52. help::save_yaml(
  53. &dirs::profiles_path()?,
  54. self,
  55. Some("# Profiles Config for Clash Verge"),
  56. )
  57. }
  58. /// 只修改current,valid和chain
  59. pub fn patch_config(&mut self, patch: IProfiles) -> Result<()> {
  60. if self.items.is_none() {
  61. self.items = Some(vec![]);
  62. }
  63. if let Some(current) = patch.current {
  64. let items = self.items.as_ref().unwrap();
  65. let some_uid = Some(current);
  66. if items.iter().any(|e| e.uid == some_uid) {
  67. self.current = some_uid;
  68. }
  69. }
  70. Ok(())
  71. }
  72. pub fn get_current(&self) -> Option<String> {
  73. self.current.clone()
  74. }
  75. /// get items ref
  76. pub fn get_items(&self) -> Option<&Vec<PrfItem>> {
  77. self.items.as_ref()
  78. }
  79. /// find the item by the uid
  80. pub fn get_item(&self, uid: &String) -> Result<&PrfItem> {
  81. if let Some(items) = self.items.as_ref() {
  82. let some_uid = Some(uid.clone());
  83. for each in items.iter() {
  84. if each.uid == some_uid {
  85. return Ok(each);
  86. }
  87. }
  88. }
  89. bail!("failed to get the profile item \"uid:{uid}\"");
  90. }
  91. /// append new item
  92. /// if the file_data is some
  93. /// then should save the data to file
  94. pub fn append_item(&mut self, mut item: PrfItem) -> Result<()> {
  95. if item.uid.is_none() {
  96. bail!("the uid should not be null");
  97. }
  98. // save the file data
  99. // move the field value after save
  100. if let Some(file_data) = item.file_data.take() {
  101. if item.file.is_none() {
  102. bail!("the file should not be null");
  103. }
  104. let file = item.file.clone().unwrap();
  105. let path = dirs::app_profiles_dir()?.join(&file);
  106. fs::File::create(path)
  107. .with_context(|| format!("failed to create file \"{}\"", file))?
  108. .write(file_data.as_bytes())
  109. .with_context(|| format!("failed to write to file \"{}\"", file))?;
  110. }
  111. if self.items.is_none() {
  112. self.items = Some(vec![]);
  113. }
  114. if let Some(items) = self.items.as_mut() {
  115. items.push(item)
  116. }
  117. self.save_file()
  118. }
  119. /// reorder items
  120. pub fn reorder(&mut self, active_id: String, over_id: String) -> Result<()> {
  121. let mut items = self.items.take().unwrap_or_default();
  122. let mut old_index = None;
  123. let mut new_index = None;
  124. for (i, _) in items.iter().enumerate() {
  125. if items[i].uid == Some(active_id.clone()) {
  126. old_index = Some(i);
  127. }
  128. if items[i].uid == Some(over_id.clone()) {
  129. new_index = Some(i);
  130. }
  131. }
  132. if old_index.is_none() || new_index.is_none() {
  133. return Ok(());
  134. }
  135. let item = items.remove(old_index.unwrap());
  136. items.insert(new_index.unwrap(), item);
  137. self.items = Some(items);
  138. self.save_file()
  139. }
  140. /// update the item value
  141. pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
  142. let mut items = self.items.take().unwrap_or_default();
  143. for each in items.iter_mut() {
  144. if each.uid == Some(uid.clone()) {
  145. patch!(each, item, itype);
  146. patch!(each, item, name);
  147. patch!(each, item, desc);
  148. patch!(each, item, file);
  149. patch!(each, item, url);
  150. patch!(each, item, selected);
  151. patch!(each, item, extra);
  152. patch!(each, item, updated);
  153. patch!(each, item, option);
  154. self.items = Some(items);
  155. return self.save_file();
  156. }
  157. }
  158. self.items = Some(items);
  159. bail!("failed to find the profile item \"uid:{uid}\"")
  160. }
  161. /// be used to update the remote item
  162. /// only patch `updated` `extra` `file_data`
  163. pub fn update_item(&mut self, uid: String, mut item: PrfItem) -> Result<()> {
  164. if self.items.is_none() {
  165. self.items = Some(vec![]);
  166. }
  167. // find the item
  168. let _ = self.get_item(&uid)?;
  169. if let Some(items) = self.items.as_mut() {
  170. let some_uid = Some(uid.clone());
  171. for each in items.iter_mut() {
  172. if each.uid == some_uid {
  173. each.extra = item.extra;
  174. each.updated = item.updated;
  175. each.home = item.home;
  176. each.option = PrfOption::merge(each.option.clone(), item.option);
  177. // save the file data
  178. // move the field value after save
  179. if let Some(file_data) = item.file_data.take() {
  180. let file = each.file.take();
  181. let file =
  182. file.unwrap_or(item.file.take().unwrap_or(format!("{}.yaml", &uid)));
  183. // the file must exists
  184. each.file = Some(file.clone());
  185. let path = dirs::app_profiles_dir()?.join(&file);
  186. fs::File::create(path)
  187. .with_context(|| format!("failed to create file \"{}\"", file))?
  188. .write(file_data.as_bytes())
  189. .with_context(|| format!("failed to write to file \"{}\"", file))?;
  190. }
  191. break;
  192. }
  193. }
  194. }
  195. self.save_file()
  196. }
  197. /// delete item
  198. /// if delete the current then return true
  199. pub fn delete_item(&mut self, uid: String) -> Result<bool> {
  200. let current = self.current.as_ref().unwrap_or(&uid);
  201. let current = current.clone();
  202. let item = self.get_item(&uid)?;
  203. let merge_uid = item.option.as_ref().and_then(|e| e.merge.clone());
  204. let script_uid = item.option.as_ref().and_then(|e| e.script.clone());
  205. let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone());
  206. let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
  207. let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
  208. let mut items = self.items.take().unwrap_or_default();
  209. let mut index = None;
  210. let mut merge_index = None;
  211. let mut script_index = None;
  212. let mut rules_index = None;
  213. let mut proxies_index = None;
  214. let mut groups_index = None;
  215. // get the index
  216. for (i, _) in items.iter().enumerate() {
  217. if items[i].uid == Some(uid.clone()) {
  218. index = Some(i);
  219. break;
  220. }
  221. }
  222. if let Some(index) = index {
  223. if let Some(file) = items.remove(index).file {
  224. let _ = dirs::app_profiles_dir().map(|path| {
  225. let path = path.join(file);
  226. if path.exists() {
  227. let _ = fs::remove_file(path);
  228. }
  229. });
  230. }
  231. }
  232. // get the merge index
  233. for (i, _) in items.iter().enumerate() {
  234. if items[i].uid == merge_uid {
  235. merge_index = Some(i);
  236. break;
  237. }
  238. }
  239. if let Some(index) = merge_index {
  240. if let Some(file) = items.remove(index).file {
  241. let _ = dirs::app_profiles_dir().map(|path| {
  242. let path = path.join(file);
  243. if path.exists() {
  244. let _ = fs::remove_file(path);
  245. }
  246. });
  247. }
  248. }
  249. // get the script index
  250. for (i, _) in items.iter().enumerate() {
  251. if items[i].uid == script_uid {
  252. script_index = Some(i);
  253. break;
  254. }
  255. }
  256. if let Some(index) = script_index {
  257. if let Some(file) = items.remove(index).file {
  258. let _ = dirs::app_profiles_dir().map(|path| {
  259. let path = path.join(file);
  260. if path.exists() {
  261. let _ = fs::remove_file(path);
  262. }
  263. });
  264. }
  265. }
  266. // get the rules index
  267. for (i, _) in items.iter().enumerate() {
  268. if items[i].uid == rules_uid {
  269. rules_index = Some(i);
  270. break;
  271. }
  272. }
  273. if let Some(index) = rules_index {
  274. if let Some(file) = items.remove(index).file {
  275. let _ = dirs::app_profiles_dir().map(|path| {
  276. let path = path.join(file);
  277. if path.exists() {
  278. let _ = fs::remove_file(path);
  279. }
  280. });
  281. }
  282. }
  283. // get the proxies index
  284. for (i, _) in items.iter().enumerate() {
  285. if items[i].uid == proxies_uid {
  286. proxies_index = Some(i);
  287. break;
  288. }
  289. }
  290. if let Some(index) = proxies_index {
  291. if let Some(file) = items.remove(index).file {
  292. let _ = dirs::app_profiles_dir().map(|path| {
  293. let path = path.join(file);
  294. if path.exists() {
  295. let _ = fs::remove_file(path);
  296. }
  297. });
  298. }
  299. }
  300. // get the groups index
  301. for (i, _) in items.iter().enumerate() {
  302. if items[i].uid == groups_uid {
  303. groups_index = Some(i);
  304. break;
  305. }
  306. }
  307. if let Some(index) = groups_index {
  308. if let Some(file) = items.remove(index).file {
  309. let _ = dirs::app_profiles_dir().map(|path| {
  310. let path = path.join(file);
  311. if path.exists() {
  312. let _ = fs::remove_file(path);
  313. }
  314. });
  315. }
  316. }
  317. // delete the original uid
  318. if current == uid {
  319. self.current = match !items.is_empty() {
  320. true => items[0].uid.clone(),
  321. false => None,
  322. };
  323. }
  324. self.items = Some(items);
  325. self.save_file()?;
  326. Ok(current == uid)
  327. }
  328. /// 获取current指向的订阅内容
  329. pub fn current_mapping(&self) -> Result<Mapping> {
  330. match (self.current.as_ref(), self.items.as_ref()) {
  331. (Some(current), Some(items)) => {
  332. if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
  333. let file_path = match item.file.as_ref() {
  334. Some(file) => dirs::app_profiles_dir()?.join(file),
  335. None => bail!("failed to get the file field"),
  336. };
  337. return help::read_mapping(&file_path);
  338. }
  339. bail!("failed to find the current profile \"uid:{current}\"");
  340. }
  341. _ => Ok(Mapping::new()),
  342. }
  343. }
  344. /// 获取current指向的订阅的merge
  345. pub fn current_merge(&self) -> Option<String> {
  346. match (self.current.as_ref(), self.items.as_ref()) {
  347. (Some(current), Some(items)) => {
  348. if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
  349. let merge = item.option.as_ref().and_then(|e| e.merge.clone());
  350. return merge;
  351. }
  352. None
  353. }
  354. _ => None,
  355. }
  356. }
  357. /// 获取current指向的订阅的script
  358. pub fn current_script(&self) -> Option<String> {
  359. match (self.current.as_ref(), self.items.as_ref()) {
  360. (Some(current), Some(items)) => {
  361. if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
  362. let script = item.option.as_ref().and_then(|e| e.script.clone());
  363. return script;
  364. }
  365. None
  366. }
  367. _ => None,
  368. }
  369. }
  370. /// 获取current指向的订阅的rules
  371. pub fn current_rules(&self) -> Option<String> {
  372. match (self.current.as_ref(), self.items.as_ref()) {
  373. (Some(current), Some(items)) => {
  374. if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
  375. let rules = item.option.as_ref().and_then(|e| e.rules.clone());
  376. return rules;
  377. }
  378. None
  379. }
  380. _ => None,
  381. }
  382. }
  383. /// 获取current指向的订阅的proxies
  384. pub fn current_proxies(&self) -> Option<String> {
  385. match (self.current.as_ref(), self.items.as_ref()) {
  386. (Some(current), Some(items)) => {
  387. if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
  388. let proxies = item.option.as_ref().and_then(|e| e.proxies.clone());
  389. return proxies;
  390. }
  391. None
  392. }
  393. _ => None,
  394. }
  395. }
  396. /// 获取current指向的订阅的groups
  397. pub fn current_groups(&self) -> Option<String> {
  398. match (self.current.as_ref(), self.items.as_ref()) {
  399. (Some(current), Some(items)) => {
  400. if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
  401. let groups = item.option.as_ref().and_then(|e| e.groups.clone());
  402. return groups;
  403. }
  404. None
  405. }
  406. _ => None,
  407. }
  408. }
  409. }