profiles.rs 9.0 KB

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