init.rs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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 res_dir = dirs::app_resources_dir()?;
  163. if !app_dir.exists() {
  164. let _ = fs::create_dir_all(&app_dir);
  165. }
  166. if !res_dir.exists() {
  167. let _ = fs::create_dir_all(&res_dir);
  168. }
  169. #[cfg(target_os = "windows")]
  170. let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
  171. #[cfg(not(target_os = "windows"))]
  172. let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
  173. // copy the resource file
  174. // if the source file is newer than the destination file, copy it over
  175. for file in file_list.iter() {
  176. let src_path = res_dir.join(file);
  177. let dest_path = app_dir.join(file);
  178. let handle_copy = || {
  179. match fs::copy(&src_path, &dest_path) {
  180. Ok(_) => log::debug!(target: "app", "resources copied '{file}'"),
  181. Err(err) => {
  182. log::error!(target: "app", "failed to copy resources '{file}', {err}")
  183. }
  184. };
  185. };
  186. if src_path.exists() && !dest_path.exists() {
  187. handle_copy();
  188. continue;
  189. }
  190. let src_modified = fs::metadata(&src_path).and_then(|m| m.modified());
  191. let dest_modified = fs::metadata(&dest_path).and_then(|m| m.modified());
  192. match (src_modified, dest_modified) {
  193. (Ok(src_modified), Ok(dest_modified)) => {
  194. if src_modified > dest_modified {
  195. handle_copy();
  196. } else {
  197. log::debug!(target: "app", "skipping resource copy '{file}'");
  198. }
  199. }
  200. _ => {
  201. log::debug!(target: "app", "failed to get modified '{file}'");
  202. handle_copy();
  203. }
  204. };
  205. }
  206. Ok(())
  207. }
  208. /// initialize url scheme
  209. #[cfg(target_os = "windows")]
  210. pub fn init_scheme() -> Result<()> {
  211. use tauri::utils::platform::current_exe;
  212. use winreg::enums::*;
  213. use winreg::RegKey;
  214. let app_exe = current_exe()?;
  215. let app_exe = dunce::canonicalize(app_exe)?;
  216. let app_exe = app_exe.to_string_lossy().into_owned();
  217. let hkcu = RegKey::predef(HKEY_CURRENT_USER);
  218. let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
  219. clash.set_value("", &"Clash Verge")?;
  220. clash.set_value("URL Protocol", &"Clash Verge URL Scheme Protocol")?;
  221. let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
  222. default_icon.set_value("", &app_exe)?;
  223. let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
  224. command.set_value("", &format!("{app_exe} \"%1\""))?;
  225. Ok(())
  226. }
  227. #[cfg(target_os = "linux")]
  228. pub fn init_scheme() -> Result<()> {
  229. let output = std::process::Command::new("xdg-mime")
  230. .arg("default")
  231. .arg("clash-verge.desktop")
  232. .arg("x-scheme-handler/clash")
  233. .output()?;
  234. if !output.status.success() {
  235. return Err(anyhow::anyhow!(
  236. "failed to set clash scheme, {}",
  237. String::from_utf8_lossy(&output.stderr)
  238. ));
  239. }
  240. Ok(())
  241. }
  242. #[cfg(target_os = "macos")]
  243. pub fn init_scheme() -> Result<()> {
  244. Ok(())
  245. }
  246. pub fn startup_script() -> Result<()> {
  247. let path = {
  248. let verge = Config::verge();
  249. let verge = verge.latest();
  250. verge.startup_script.clone().unwrap_or("".to_string())
  251. };
  252. if !path.is_empty() {
  253. let mut shell = "";
  254. if path.ends_with(".sh") {
  255. shell = "bash";
  256. }
  257. if path.ends_with(".ps1") {
  258. shell = "powershell";
  259. }
  260. if path.ends_with(".bat") {
  261. shell = "cmd";
  262. }
  263. if shell.is_empty() {
  264. return Err(anyhow::anyhow!("unsupported script: {path}"));
  265. }
  266. let current_dir = PathBuf::from(path.clone());
  267. if !current_dir.exists() {
  268. return Err(anyhow::anyhow!("script not found: {path}"));
  269. }
  270. let current_dir = current_dir.parent();
  271. match current_dir {
  272. Some(dir) => {
  273. let _ = Command::new(shell)
  274. .current_dir(dir.to_path_buf())
  275. .args(&[path])
  276. .output()?;
  277. }
  278. None => {
  279. let _ = Command::new(shell).args(&[path]).output()?;
  280. }
  281. }
  282. }
  283. Ok(())
  284. }