init.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. use crate::config::*;
  2. use crate::utils::{dirs, help};
  3. use anyhow::Result;
  4. use chrono::{Local, TimeZone};
  5. use log::LevelFilter;
  6. use log4rs::append::console::ConsoleAppender;
  7. use log4rs::append::file::FileAppender;
  8. use log4rs::config::{Appender, Logger, Root};
  9. use log4rs::encode::pattern::PatternEncoder;
  10. use std::fs::{self, DirEntry};
  11. use std::path::PathBuf;
  12. use std::str::FromStr;
  13. use tauri::api::process::Command;
  14. /// initialize this instance's log file
  15. fn init_log() -> Result<()> {
  16. let log_dir = dirs::app_logs_dir()?;
  17. if !log_dir.exists() {
  18. let _ = fs::create_dir_all(&log_dir);
  19. }
  20. let log_level = Config::verge().data().get_log_level();
  21. if log_level == LevelFilter::Off {
  22. return Ok(());
  23. }
  24. let local_time = Local::now().format("%Y-%m-%d-%H%M").to_string();
  25. let log_file = format!("{}.log", local_time);
  26. let log_file = log_dir.join(log_file);
  27. let log_pattern = match log_level {
  28. LevelFilter::Trace => "{d(%Y-%m-%d %H:%M:%S)} {l} [{M}] - {m}{n}",
  29. _ => "{d(%Y-%m-%d %H:%M:%S)} {l} - {m}{n}",
  30. };
  31. let encode = Box::new(PatternEncoder::new(log_pattern));
  32. let stdout = ConsoleAppender::builder().encoder(encode.clone()).build();
  33. let tofile = FileAppender::builder().encoder(encode).build(log_file)?;
  34. let mut logger_builder = Logger::builder();
  35. let mut root_builder = Root::builder();
  36. let log_more = log_level == LevelFilter::Trace || log_level == LevelFilter::Debug;
  37. #[cfg(feature = "verge-dev")]
  38. {
  39. logger_builder = logger_builder.appenders(["file", "stdout"]);
  40. if log_more {
  41. root_builder = root_builder.appenders(["file", "stdout"]);
  42. } else {
  43. root_builder = root_builder.appenders(["stdout"]);
  44. }
  45. }
  46. #[cfg(not(feature = "verge-dev"))]
  47. {
  48. logger_builder = logger_builder.appenders(["file"]);
  49. if log_more {
  50. root_builder = root_builder.appenders(["file"]);
  51. }
  52. }
  53. let (config, _) = log4rs::config::Config::builder()
  54. .appender(Appender::builder().build("stdout", Box::new(stdout)))
  55. .appender(Appender::builder().build("file", Box::new(tofile)))
  56. .logger(logger_builder.additive(false).build("app", log_level))
  57. .build_lossy(root_builder.build(log_level));
  58. log4rs::init_config(config)?;
  59. Ok(())
  60. }
  61. /// 删除log文件
  62. pub fn delete_log() -> Result<()> {
  63. let log_dir = dirs::app_logs_dir()?;
  64. if !log_dir.exists() {
  65. return Ok(());
  66. }
  67. let auto_log_clean = {
  68. let verge = Config::verge();
  69. let verge = verge.data();
  70. verge.auto_log_clean.unwrap_or(0)
  71. };
  72. let day = match auto_log_clean {
  73. 1 => 7,
  74. 2 => 30,
  75. 3 => 90,
  76. _ => return Ok(()),
  77. };
  78. log::debug!(target: "app", "try to delete log files, day: {day}");
  79. // %Y-%m-%d to NaiveDateTime
  80. let parse_time_str = |s: &str| {
  81. let sa: Vec<&str> = s.split('-').collect();
  82. if sa.len() != 4 {
  83. return Err(anyhow::anyhow!("invalid time str"));
  84. }
  85. let year = i32::from_str(sa[0])?;
  86. let month = u32::from_str(sa[1])?;
  87. let day = u32::from_str(sa[2])?;
  88. let time = chrono::NaiveDate::from_ymd_opt(year, month, day)
  89. .ok_or(anyhow::anyhow!("invalid time str"))?
  90. .and_hms_opt(0, 0, 0)
  91. .ok_or(anyhow::anyhow!("invalid time str"))?;
  92. Ok(time)
  93. };
  94. let process_file = |file: DirEntry| -> Result<()> {
  95. let file_name = file.file_name();
  96. let file_name = file_name.to_str().unwrap_or_default();
  97. if file_name.ends_with(".log") {
  98. let now = Local::now();
  99. let created_time = parse_time_str(&file_name[0..file_name.len() - 4])?;
  100. let file_time = Local
  101. .from_local_datetime(&created_time)
  102. .single()
  103. .ok_or(anyhow::anyhow!("invalid local datetime"))?;
  104. let duration = now.signed_duration_since(file_time);
  105. if duration.num_days() > day {
  106. let file_path = file.path();
  107. let _ = fs::remove_file(file_path);
  108. log::info!(target: "app", "delete log file: {file_name}");
  109. }
  110. }
  111. Ok(())
  112. };
  113. for file in fs::read_dir(&log_dir)?.flatten() {
  114. let _ = process_file(file);
  115. }
  116. let service_log_dir = log_dir.join("service");
  117. for file in fs::read_dir(service_log_dir)?.flatten() {
  118. let _ = process_file(file);
  119. }
  120. Ok(())
  121. }
  122. /// Initialize all the config files
  123. /// before tauri setup
  124. pub fn init_config() -> Result<()> {
  125. let _ = dirs::init_portable_flag();
  126. let _ = init_log();
  127. let _ = delete_log();
  128. crate::log_err!(dirs::app_home_dir().map(|app_dir| {
  129. if !app_dir.exists() {
  130. let _ = fs::create_dir_all(&app_dir);
  131. }
  132. }));
  133. crate::log_err!(dirs::app_profiles_dir().map(|profiles_dir| {
  134. if !profiles_dir.exists() {
  135. let _ = fs::create_dir_all(&profiles_dir);
  136. }
  137. }));
  138. crate::log_err!(dirs::clash_path().map(|path| {
  139. if !path.exists() {
  140. help::save_yaml(&path, &IClashTemp::template().0, Some("# Clash Vergeasu"))?;
  141. }
  142. <Result<()>>::Ok(())
  143. }));
  144. crate::log_err!(dirs::verge_path().map(|path| {
  145. if !path.exists() {
  146. help::save_yaml(&path, &IVerge::template(), Some("# Clash Verge"))?;
  147. }
  148. <Result<()>>::Ok(())
  149. }));
  150. crate::log_err!(dirs::profiles_path().map(|path| {
  151. if !path.exists() {
  152. help::save_yaml(&path, &IProfiles::template(), Some("# Clash Verge"))?;
  153. }
  154. <Result<()>>::Ok(())
  155. }));
  156. Ok(())
  157. }
  158. /// initialize app resources
  159. /// after tauri setup
  160. pub fn init_resources() -> Result<()> {
  161. let app_dir = dirs::app_home_dir()?;
  162. let test_dir = app_dir.join("test");
  163. let res_dir = dirs::app_resources_dir()?;
  164. if !app_dir.exists() {
  165. let _ = fs::create_dir_all(&app_dir);
  166. }
  167. if !test_dir.exists() {
  168. let _ = fs::create_dir_all(&test_dir);
  169. }
  170. if !res_dir.exists() {
  171. let _ = fs::create_dir_all(&res_dir);
  172. }
  173. #[cfg(target_os = "windows")]
  174. let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
  175. #[cfg(not(target_os = "windows"))]
  176. let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
  177. // copy the resource file
  178. // if the source file is newer than the destination file, copy it over
  179. for file in file_list.iter() {
  180. let src_path = res_dir.join(file);
  181. let dest_path = app_dir.join(file);
  182. let test_dest_path = test_dir.join(file);
  183. let handle_copy = |dest: &PathBuf| {
  184. match fs::copy(&src_path, dest) {
  185. Ok(_) => log::debug!(target: "app", "resources copied '{file}'"),
  186. Err(err) => {
  187. log::error!(target: "app", "failed to copy resources '{file}', {err}")
  188. }
  189. };
  190. };
  191. if src_path.exists() && !test_dest_path.exists() {
  192. handle_copy(&test_dest_path);
  193. }
  194. if src_path.exists() && !dest_path.exists() {
  195. handle_copy(&dest_path);
  196. continue;
  197. }
  198. let src_modified = fs::metadata(&src_path).and_then(|m| m.modified());
  199. let dest_modified = fs::metadata(&dest_path).and_then(|m| m.modified());
  200. match (src_modified, dest_modified) {
  201. (Ok(src_modified), Ok(dest_modified)) => {
  202. if src_modified > dest_modified {
  203. handle_copy(&dest_path);
  204. } else {
  205. log::debug!(target: "app", "skipping resource copy '{file}'");
  206. }
  207. }
  208. _ => {
  209. log::debug!(target: "app", "failed to get modified '{file}'");
  210. handle_copy(&dest_path);
  211. }
  212. };
  213. }
  214. Ok(())
  215. }
  216. /// initialize url scheme
  217. #[cfg(target_os = "windows")]
  218. pub fn init_scheme() -> Result<()> {
  219. use tauri::utils::platform::current_exe;
  220. use winreg::enums::*;
  221. use winreg::RegKey;
  222. let app_exe = current_exe()?;
  223. let app_exe = dunce::canonicalize(app_exe)?;
  224. let app_exe = app_exe.to_string_lossy().into_owned();
  225. let hkcu = RegKey::predef(HKEY_CURRENT_USER);
  226. let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
  227. clash.set_value("", &"Clash Verge")?;
  228. clash.set_value("URL Protocol", &"Clash Verge URL Scheme Protocol")?;
  229. let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
  230. default_icon.set_value("", &app_exe)?;
  231. let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
  232. command.set_value("", &format!("{app_exe} \"%1\""))?;
  233. Ok(())
  234. }
  235. #[cfg(target_os = "linux")]
  236. pub fn init_scheme() -> Result<()> {
  237. let output = std::process::Command::new("xdg-mime")
  238. .arg("default")
  239. .arg("clash-verge.desktop")
  240. .arg("x-scheme-handler/clash")
  241. .output()?;
  242. if !output.status.success() {
  243. return Err(anyhow::anyhow!(
  244. "failed to set clash scheme, {}",
  245. String::from_utf8_lossy(&output.stderr)
  246. ));
  247. }
  248. Ok(())
  249. }
  250. #[cfg(target_os = "macos")]
  251. pub fn init_scheme() -> Result<()> {
  252. Ok(())
  253. }
  254. pub fn startup_script() -> Result<()> {
  255. let path = {
  256. let verge = Config::verge();
  257. let verge = verge.latest();
  258. verge.startup_script.clone().unwrap_or("".to_string())
  259. };
  260. if !path.is_empty() {
  261. let mut shell = "";
  262. if path.ends_with(".sh") {
  263. shell = "bash";
  264. }
  265. if path.ends_with(".ps1") {
  266. shell = "powershell";
  267. }
  268. if path.ends_with(".bat") {
  269. shell = "powershell";
  270. }
  271. if shell.is_empty() {
  272. return Err(anyhow::anyhow!("unsupported script: {path}"));
  273. }
  274. let current_dir = PathBuf::from(path.clone());
  275. if !current_dir.exists() {
  276. return Err(anyhow::anyhow!("script not found: {path}"));
  277. }
  278. let current_dir = current_dir.parent();
  279. match current_dir {
  280. Some(dir) => {
  281. let _ = Command::new(shell)
  282. .current_dir(dir.to_path_buf())
  283. .args([path])
  284. .output()?;
  285. }
  286. None => {
  287. let _ = Command::new(shell).args([path]).output()?;
  288. }
  289. }
  290. }
  291. Ok(())
  292. }