123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- use crate::config::*;
- use crate::utils::{dirs, help};
- use anyhow::Result;
- use chrono::{Local, TimeZone};
- use log::LevelFilter;
- use log4rs::append::console::ConsoleAppender;
- use log4rs::append::file::FileAppender;
- use log4rs::config::{Appender, Logger, Root};
- use log4rs::encode::pattern::PatternEncoder;
- use std::fs::{self, DirEntry};
- use std::path::PathBuf;
- use std::str::FromStr;
- use tauri::api::process::Command;
- /// initialize this instance's log file
- fn init_log() -> Result<()> {
- let log_dir = dirs::app_logs_dir()?;
- if !log_dir.exists() {
- let _ = fs::create_dir_all(&log_dir);
- }
- let log_level = Config::verge().data().get_log_level();
- if log_level == LevelFilter::Off {
- return Ok(());
- }
- let local_time = Local::now().format("%Y-%m-%d-%H%M").to_string();
- let log_file = format!("{}.log", local_time);
- let log_file = log_dir.join(log_file);
- let log_pattern = match log_level {
- LevelFilter::Trace => "{d(%Y-%m-%d %H:%M:%S)} {l} [{M}] - {m}{n}",
- _ => "{d(%Y-%m-%d %H:%M:%S)} {l} - {m}{n}",
- };
- let encode = Box::new(PatternEncoder::new(log_pattern));
- let stdout = ConsoleAppender::builder().encoder(encode.clone()).build();
- let tofile = FileAppender::builder().encoder(encode).build(log_file)?;
- let mut logger_builder = Logger::builder();
- let mut root_builder = Root::builder();
- let log_more = log_level == LevelFilter::Trace || log_level == LevelFilter::Debug;
- #[cfg(feature = "verge-dev")]
- {
- logger_builder = logger_builder.appenders(["file", "stdout"]);
- if log_more {
- root_builder = root_builder.appenders(["file", "stdout"]);
- } else {
- root_builder = root_builder.appenders(["stdout"]);
- }
- }
- #[cfg(not(feature = "verge-dev"))]
- {
- logger_builder = logger_builder.appenders(["file"]);
- if log_more {
- root_builder = root_builder.appenders(["file"]);
- }
- }
- let (config, _) = log4rs::config::Config::builder()
- .appender(Appender::builder().build("stdout", Box::new(stdout)))
- .appender(Appender::builder().build("file", Box::new(tofile)))
- .logger(logger_builder.additive(false).build("app", log_level))
- .build_lossy(root_builder.build(log_level));
- log4rs::init_config(config)?;
- Ok(())
- }
- /// 删除log文件
- pub fn delete_log() -> Result<()> {
- let log_dir = dirs::app_logs_dir()?;
- if !log_dir.exists() {
- return Ok(());
- }
- let auto_log_clean = {
- let verge = Config::verge();
- let verge = verge.data();
- verge.auto_log_clean.unwrap_or(0)
- };
- let day = match auto_log_clean {
- 1 => 7,
- 2 => 30,
- 3 => 90,
- _ => return Ok(()),
- };
- log::debug!(target: "app", "try to delete log files, day: {day}");
- // %Y-%m-%d to NaiveDateTime
- let parse_time_str = |s: &str| {
- let sa: Vec<&str> = s.split('-').collect();
- if sa.len() != 4 {
- return Err(anyhow::anyhow!("invalid time str"));
- }
- let year = i32::from_str(sa[0])?;
- let month = u32::from_str(sa[1])?;
- let day = u32::from_str(sa[2])?;
- let time = chrono::NaiveDate::from_ymd_opt(year, month, day)
- .ok_or(anyhow::anyhow!("invalid time str"))?
- .and_hms_opt(0, 0, 0)
- .ok_or(anyhow::anyhow!("invalid time str"))?;
- Ok(time)
- };
- let process_file = |file: DirEntry| -> Result<()> {
- let file_name = file.file_name();
- let file_name = file_name.to_str().unwrap_or_default();
- if file_name.ends_with(".log") {
- let now = Local::now();
- let created_time = parse_time_str(&file_name[0..file_name.len() - 4])?;
- let file_time = Local
- .from_local_datetime(&created_time)
- .single()
- .ok_or(anyhow::anyhow!("invalid local datetime"))?;
- let duration = now.signed_duration_since(file_time);
- if duration.num_days() > day {
- let file_path = file.path();
- let _ = fs::remove_file(file_path);
- log::info!(target: "app", "delete log file: {file_name}");
- }
- }
- Ok(())
- };
- for file in fs::read_dir(&log_dir)?.flatten() {
- let _ = process_file(file);
- }
- let service_log_dir = log_dir.join("service");
- for file in fs::read_dir(service_log_dir)?.flatten() {
- let _ = process_file(file);
- }
- Ok(())
- }
- /// Initialize all the config files
- /// before tauri setup
- pub fn init_config() -> Result<()> {
- let _ = dirs::init_portable_flag();
- let _ = init_log();
- let _ = delete_log();
- crate::log_err!(dirs::app_home_dir().map(|app_dir| {
- if !app_dir.exists() {
- let _ = fs::create_dir_all(&app_dir);
- }
- }));
- crate::log_err!(dirs::app_profiles_dir().map(|profiles_dir| {
- if !profiles_dir.exists() {
- let _ = fs::create_dir_all(&profiles_dir);
- }
- }));
- crate::log_err!(dirs::clash_path().map(|path| {
- if !path.exists() {
- help::save_yaml(&path, &IClashTemp::template().0, Some("# Clash Vergeasu"))?;
- }
- <Result<()>>::Ok(())
- }));
- crate::log_err!(dirs::verge_path().map(|path| {
- if !path.exists() {
- help::save_yaml(&path, &IVerge::template(), Some("# Clash Verge"))?;
- }
- <Result<()>>::Ok(())
- }));
- crate::log_err!(dirs::profiles_path().map(|path| {
- if !path.exists() {
- help::save_yaml(&path, &IProfiles::template(), Some("# Clash Verge"))?;
- }
- <Result<()>>::Ok(())
- }));
- Ok(())
- }
- /// initialize app resources
- /// after tauri setup
- pub fn init_resources() -> Result<()> {
- let app_dir = dirs::app_home_dir()?;
- let test_dir = app_dir.join("test");
- let res_dir = dirs::app_resources_dir()?;
- if !app_dir.exists() {
- let _ = fs::create_dir_all(&app_dir);
- }
- if !test_dir.exists() {
- let _ = fs::create_dir_all(&test_dir);
- }
- if !res_dir.exists() {
- let _ = fs::create_dir_all(&res_dir);
- }
- #[cfg(target_os = "windows")]
- let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
- #[cfg(not(target_os = "windows"))]
- let file_list = ["Country.mmdb", "geoip.dat", "geosite.dat"];
- // copy the resource file
- // if the source file is newer than the destination file, copy it over
- for file in file_list.iter() {
- let src_path = res_dir.join(file);
- let dest_path = app_dir.join(file);
- let test_dest_path = test_dir.join(file);
- let handle_copy = |dest: &PathBuf| {
- match fs::copy(&src_path, dest) {
- Ok(_) => log::debug!(target: "app", "resources copied '{file}'"),
- Err(err) => {
- log::error!(target: "app", "failed to copy resources '{file}', {err}")
- }
- };
- };
- if src_path.exists() && !test_dest_path.exists() {
- handle_copy(&test_dest_path);
- }
- if src_path.exists() && !dest_path.exists() {
- handle_copy(&dest_path);
- continue;
- }
- let src_modified = fs::metadata(&src_path).and_then(|m| m.modified());
- let dest_modified = fs::metadata(&dest_path).and_then(|m| m.modified());
- match (src_modified, dest_modified) {
- (Ok(src_modified), Ok(dest_modified)) => {
- if src_modified > dest_modified {
- handle_copy(&dest_path);
- } else {
- log::debug!(target: "app", "skipping resource copy '{file}'");
- }
- }
- _ => {
- log::debug!(target: "app", "failed to get modified '{file}'");
- handle_copy(&dest_path);
- }
- };
- }
- Ok(())
- }
- /// initialize url scheme
- #[cfg(target_os = "windows")]
- pub fn init_scheme() -> Result<()> {
- use tauri::utils::platform::current_exe;
- use winreg::enums::*;
- use winreg::RegKey;
- let app_exe = current_exe()?;
- let app_exe = dunce::canonicalize(app_exe)?;
- let app_exe = app_exe.to_string_lossy().into_owned();
- let hkcu = RegKey::predef(HKEY_CURRENT_USER);
- let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
- clash.set_value("", &"Clash Verge")?;
- clash.set_value("URL Protocol", &"Clash Verge URL Scheme Protocol")?;
- let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
- default_icon.set_value("", &app_exe)?;
- let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
- command.set_value("", &format!("{app_exe} \"%1\""))?;
- Ok(())
- }
- #[cfg(target_os = "linux")]
- pub fn init_scheme() -> Result<()> {
- let output = std::process::Command::new("xdg-mime")
- .arg("default")
- .arg("clash-verge.desktop")
- .arg("x-scheme-handler/clash")
- .output()?;
- if !output.status.success() {
- return Err(anyhow::anyhow!(
- "failed to set clash scheme, {}",
- String::from_utf8_lossy(&output.stderr)
- ));
- }
- Ok(())
- }
- #[cfg(target_os = "macos")]
- pub fn init_scheme() -> Result<()> {
- Ok(())
- }
- pub fn startup_script() -> Result<()> {
- let path = {
- let verge = Config::verge();
- let verge = verge.latest();
- verge.startup_script.clone().unwrap_or("".to_string())
- };
- if !path.is_empty() {
- let mut shell = "";
- if path.ends_with(".sh") {
- shell = "bash";
- }
- if path.ends_with(".ps1") {
- shell = "powershell";
- }
- if path.ends_with(".bat") {
- shell = "powershell";
- }
- if shell.is_empty() {
- return Err(anyhow::anyhow!("unsupported script: {path}"));
- }
- let current_dir = PathBuf::from(path.clone());
- if !current_dir.exists() {
- return Err(anyhow::anyhow!("script not found: {path}"));
- }
- let current_dir = current_dir.parent();
- match current_dir {
- Some(dir) => {
- let _ = Command::new(shell)
- .current_dir(dir.to_path_buf())
- .args([path])
- .output()?;
- }
- None => {
- let _ = Command::new(shell).args([path]).output()?;
- }
- }
- }
- Ok(())
- }
|