diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3a949bf..b066db0 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -44,38 +44,3 @@ jobs: name: Linux_Build path: artifacts retention-days: 5 - - build-windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Mihomo - run: | - curl -L https://github.com/MetaCubeX/mihomo/releases/download/v1.18.1/mihomo-windows-amd64-v1.18.1.zip --output mihomo.zip - 7z x mihomo.zip - mihomo-windows-amd64.exe -d Example -f Example/basic_clash_config.yaml & - - - name: Download Dependencies - run: cd clashtui && cargo fetch - - - name: Build - run: cd clashtui && cargo build --verbose && cargo build --release - - # - name: Run tests - # run: cd clashtui && cargo test --all --verbose - - - name: Build Version - run: cd clashtui && cargo r -- -v - - - name: Pre Upload - run: | - mkdir artifacts - mv ./clashtui/target/debug/clashtui.exe ./artifacts/clashtui.debug.exe - - - name: upload artifacts - uses: actions/upload-artifact@v4 - with: - name: Windows_Build - path: artifacts - retention-days: 5 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 43fa5b4..a4fbafb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -58,59 +58,10 @@ jobs: path: artifacts retention-days: 5 - build-windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Mihomo - run: | - curl -L https://github.com/MetaCubeX/mihomo/releases/download/v1.18.1/mihomo-windows-amd64-v1.18.1.zip --output mihomo.zip - 7z x mihomo.zip - mihomo-windows-amd64.exe -d Example -f Example/basic_clash_config.yaml & - - - name: Cache Target - uses: actions/cache@v4 - with: - path: | - ./clashtui/target - ~/.cargo - key: ci-${{ runner.os }}-${{ hashFiles('./clashtui/Cargo.lock') }} - restore-keys: | - ci-${{ runner.os }}-${{ hashFiles('./clashtui/Cargo.lock') }} - ci-${{ runner.os }}- - - - name: Download Dependencies - run: cd clashtui && cargo fetch - - # - name: Run tests - # run: cd clashtui && cargo test --all --verbose - - - name: Build - run: cd clashtui && cargo build --verbose && cargo build --release - - - name: Build Version - run: cd clashtui && cargo r -- -v - - - name: Pre Upload - run: | - mkdir artifacts - mv ./clashtui/target/release/clashtui.exe ./artifacts/clashtui.release.exe - mv ./clashtui/target/debug/clashtui.exe ./artifacts/clashtui.debug.exe - - - name: upload artifacts - uses: actions/upload-artifact@v4 - with: - name: Windows_Build - path: artifacts - retention-days: 5 - release: runs-on: ubuntu-latest - needs: [build-linux, build-windows] + needs: [build-linux] if: startsWith(github.ref, 'refs/tags/') diff --git a/clashtui/Cargo.lock b/clashtui/Cargo.lock index 3154911..b2a783b 100644 --- a/clashtui/Cargo.lock +++ b/clashtui/Cargo.lock @@ -52,7 +52,7 @@ checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" [[package]] name = "api" -version = "0.1.0" +version = "0.1.1" dependencies = [ "chrono", "minreq", @@ -180,12 +180,11 @@ dependencies = [ [[package]] name = "clashtui" -version = "0.2.4" +version = "0.2.0" dependencies = [ "api", "argh", "enumflags2", - "libc", "log", "log4rs", "nix", diff --git a/clashtui/Cargo.toml b/clashtui/Cargo.toml index 1295355..eb80308 100644 --- a/clashtui/Cargo.toml +++ b/clashtui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clashtui" -version = "0.2.4" +version = "0.2.0" edition = "2021" authors = ["Johan Chane "] @@ -30,7 +30,6 @@ log = "0.4" log4rs = {version = "1.3", default-features = false, features = ["pattern_encoder", "file_appender"]} enumflags2 = "0.7.9" nix = {version = "0.28.0", features = ["fs", "user"]} -libc = "0.2.153" regex = "1.10.3" [workspace] diff --git a/clashtui/api/Cargo.toml b/clashtui/api/Cargo.toml index 9dd16d0..bf436b0 100644 --- a/clashtui/api/Cargo.toml +++ b/clashtui/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "api" -version = "0.1.0" +version = "0.1.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/clashtui/api/src/clash.rs b/clashtui/api/src/clash.rs index 0d93c82..3c3c3c4 100644 --- a/clashtui/api/src/clash.rs +++ b/clashtui/api/src/clash.rs @@ -1,18 +1,14 @@ const DEFAULT_PAYLOAD: &str = "'{\"path\": \"\", \"payload\": \"\"}'"; -const TIMEOUT: u8 = 3; +//const TIMEOUT: u8 = 3; +const TIMEOUT: u8 = 10; // Adapting to poor network conditions. + // ToDo: Users can adjust settings based on their network quality. #[cfg(target_feature = "deprecated")] const GEO_URI: &str = "https://api.github.com/repos/MetaCubeX/meta-rules-dat/releases/latest"; #[cfg(target_feature = "deprecated")] const USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; use std::io::Result; -use std::time::SystemTime; - -use minreq::{Method, Proxy}; -use chrono::{DateTime, Local, TimeZone}; - -// format: {type: [(name, modifytime)]} -pub type ProfileTimeMap = std::collections::HashMap)>>; +use minreq::Method; #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum ProfileSectionType { @@ -21,14 +17,6 @@ pub enum ProfileSectionType { RuleProvider, } -pub fn provider_str_in_api(section_type: ProfileSectionType) -> Option { - match section_type { - ProfileSectionType::ProxyProvider => Some("proxies".to_string()), - ProfileSectionType::RuleProvider => Some("rules".to_string()), - _ => None, - } -} - trait ResProcess { fn process(self) -> core::result::Result; } @@ -140,6 +128,7 @@ impl ClashUtil { self.request(Method::Patch, "/configs", Some(payload)) } + /*** update_providers_with_api pub fn update_providers(&self, provider_type: ProfileSectionType) -> Result)>> { self.extract_net_providers(provider_type).and_then(|names| self.update_providers_helper(names, provider_type)) } @@ -184,7 +173,9 @@ impl ClashUtil { } // Sometime mihomo updated the provider but not update it to the file. - pub fn extract_provider_utimes_with_api(&self, provider_type: ProfileSectionType) -> Result)>>{ + pub fn extract_provider_utimes_with_api(&self, provider_type: ProfileSectionType) -> Result)>>{ + use chrono::{DateTime, Local}; + let sub_url = format!("/providers/{}", provider_str_in_api(provider_type).unwrap()); let response_str = self.request(Method::Get, sub_url.as_str(), None)?; @@ -219,6 +210,15 @@ impl ClashUtil { Ok(net_providers) } + pub fn provider_str_in_api(section_type: ProfileSectionType) -> Option { + match section_type { + ProfileSectionType::ProxyProvider => Some("proxies".to_string()), + ProfileSectionType::RuleProvider => Some("rules".to_string()), + _ => None, + } + } + ***/ + #[cfg(target_feature = "deprecated")] pub fn check_geo_update( &self, diff --git a/clashtui/api/src/lib.rs b/clashtui/api/src/lib.rs index a8e268e..8164fb1 100644 --- a/clashtui/api/src/lib.rs +++ b/clashtui/api/src/lib.rs @@ -5,7 +5,7 @@ mod dl_mihomo; #[cfg(target_feature = "github_api")] mod github_restful_api; -pub use clash::{ClashUtil, Resp, ProfileSectionType, ProfileTimeMap, provider_str_in_api}; +pub use clash::{ClashUtil, Resp, ProfileSectionType}; pub use config::{ClashConfig, Mode, TunStack}; #[cfg(target_feature = "github_api")] pub use github_restful_api::GithubApi; diff --git a/clashtui/src/main.rs b/clashtui/src/main.rs index 31396d1..597dc4f 100644 --- a/clashtui/src/main.rs +++ b/clashtui/src/main.rs @@ -23,11 +23,6 @@ fn main() { let mut flags = Flags::empty(); - // ## Is CliMode - if cli_env.update_all_profiles { - flags.insert(Flag::CliMode) - } - // ## Setup logging as early as possible. So We can log. let config_dir = load_app_dir(&mut flags); setup_logging(config_dir.join("clashtui.log").to_str().unwrap()); diff --git a/clashtui/src/utils/flags.rs b/clashtui/src/utils/flags.rs index 75b8154..cc44206 100644 --- a/clashtui/src/utils/flags.rs +++ b/clashtui/src/utils/flags.rs @@ -5,10 +5,9 @@ pub use enumflags2::BitFlags; #[bitflags] #[repr(u8)] pub enum Flag { - CliMode = 1, - FirstInit = 1 << 1, - ErrorDuringInit = 1 << 2, - PortableMode = 1 << 3, + FirstInit = 1, + ErrorDuringInit = 1 << 1, + PortableMode = 1 << 2, } #[cfg(test)] mod test { diff --git a/clashtui/src/utils/tui.rs b/clashtui/src/utils/tui.rs index 2c01063..c0e2877 100644 --- a/clashtui/src/utils/tui.rs +++ b/clashtui/src/utils/tui.rs @@ -18,8 +18,6 @@ use api::{ClashConfig, ClashUtil, Resp}; // format: {section_key: [(name, url, path)]} pub type NetProviderMap = std::collections::HashMap>; -// format: {type, [(name, result)]} -pub type UpdateProviderType = std::collections::HashMap)>>; #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum ProfileType { @@ -138,6 +136,7 @@ fn load_app_config( .and_then(|v| v.as_str()) .unwrap_or("clash.meta") .to_string(); + log::info!("clash_ua: {}", clash_ua); let configs = if skip_init_conf { let config_path = clashtui_dir.join("config.yaml"); diff --git a/clashtui/src/utils/tui/impl_profile.rs b/clashtui/src/utils/tui/impl_profile.rs index 269f4b4..24d9159 100644 --- a/clashtui/src/utils/tui/impl_profile.rs +++ b/clashtui/src/utils/tui/impl_profile.rs @@ -1,9 +1,4 @@ -use std::os::unix::fs::{PermissionsExt, MetadataExt}; -use std::io::BufRead; -use regex::Regex; - -use crate::utils::tui::{NetProviderMap, UpdateProviderType, ProfileType}; -use api::ProfileTimeMap; +use crate::utils::tui::{NetProviderMap, ProfileType}; use super::ClashTuiUtil; use crate::utils::{is_yaml, utils as Utils}; @@ -342,7 +337,6 @@ impl ClashTuiUtil { does_update_all: bool, ) -> std::io::Result> { self.update_profile_with_clashtui(profile_name, does_update_all) - //self.update_profile_with_api(profile_name, does_update_all) } // The advantage of using this interface for updates is that you can know the reason for update failures without needing to check mihomo's logs. The downside is that it requires resolving file permission issues. @@ -387,70 +381,6 @@ impl ClashTuiUtil { Ok(result) } - // Using api update, the user needs to check the logs to understand why the updates failed. The success rate of my testing updates is not as high as using clashtui. - #[cfg(target_feature = "deprecated")] - pub fn update_profile_with_api( - &self, - profile_name: &String, - does_update_all: bool, - ) -> std::io::Result> { - let mut result = Vec::new(); - if self.get_profile_type(profile_name) - .is_some_and(|t| t == ProfileType::Url) - { - let sub_url = self.extract_profile_url(profile_name)?; - let profile_yaml_path = self.get_profile_cache_unchecked(profile_name); - // Update the file to keep up-to-date - self.download_profile(sub_url.as_str(), &profile_yaml_path)?; - - result.push( - format!("Updated: {}, {}", profile_name, sub_url) - ); - } - - let mut provider_types = vec![ProfileSectionType::ProxyProvider]; - if does_update_all { - provider_types.push(ProfileSectionType::RuleProvider); - } - - let mut update_providers_result = UpdateProviderType::new(); - let mut update_times = ProfileTimeMap::new(); - for t in provider_types { - // Get result of update providers - update_providers_result.insert(t, self.clash_api.update_providers(t)?); - - // Get update times after update providers - if let Ok(name_times) = self.clash_api.extract_provider_utimes_with_api(t) { - update_times.insert(t, name_times); - } - } - - // Add results of updating providers - for (section_type, res) in update_providers_result { - for (name, r) in res { - // Generate duration_str - let duration_str = if let Ok(d) = Self::cal_mtime_duration(&update_times, section_type, &name) { - Utils::str_duration(d) - } else { - "No update times or can't cal the duration".to_string() - }; - - let line = match r { - Ok(_) => { - format!("Sent update request: {}, duration = '{}'", name, duration_str) - } - Err(err) => { - log::error!("Not Sent update request:{err}"); - format!("Not Sent update request: {}, duration = '{}'", name, duration_str) - } - }; - result.push(line); - } - } - - Ok(result) - } - fn download_profile(&self, url: &str, path: &PathBuf) -> std::io::Result<()> { let directory = path .parent() @@ -465,22 +395,6 @@ impl ClashTuiUtil { Ok(()) } - fn get_provider_mtime(&self, section_types: Vec, profile_yaml_path: &PathBuf) -> std::io::Result { - let mut modify_info = ProfileTimeMap::new(); - if let Ok(net_res) = self.extract_net_providers(profile_yaml_path, §ion_types) { - for (key, res) in net_res { - let name_and_times = res.into_iter().map(|(name, _, path)| { - let clash_cfg_dir = Path::new(&self.tui_cfg.clash_cfg_dir); - let time = Utils::get_mtime(clash_cfg_dir.join(path)).ok(); - (name, time) - }).collect(); - modify_info.insert(key, name_and_times); - } - } - - Ok(modify_info) - } - pub fn extract_net_providers(&self, profile_yaml_path: &PathBuf, provider_types: &Vec) -> std::io::Result { let yaml_content = std::fs::read_to_string(&profile_yaml_path)?; let parsed_yaml = match serde_yaml::from_str::(&yaml_content) { @@ -539,27 +453,10 @@ impl ClashTuiUtil { Ok(net_providers) } - // duration: now - mtime - fn cal_mtime_duration(mtimes: &ProfileTimeMap, section_type: ProfileSectionType, name: &String) -> std::io::Result { - let mt = Self::extract_the_mtime(mtimes, section_type, name).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "No the mtime in mtimes"))?; - let now = std::time::SystemTime::now(); - now.duration_since(mt).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) - } - - fn extract_the_mtime<'a>(mtimes: &'a ProfileTimeMap, section_type: ProfileSectionType, name: &String) -> &'a Option { - if let Some(times) = mtimes.get(§ion_type) { - for (n, the_mtime) in times { - if n == name { - return the_mtime; - } - } - } - - &None - } - // Check if need to correct perms of files in clash_cfg_dir. If perm is incorrect return false. pub fn check_perms_of_ccd_files(&self) -> bool { + use std::os::unix::fs::{PermissionsExt, MetadataExt}; + let dir = Path::new(self.tui_cfg.clash_cfg_dir.as_str()); //let group_name = Utils::get_file_group_name(&dir.to_path_buf()); //if group_name.is_none() { @@ -589,6 +486,112 @@ impl ClashTuiUtil { return true; } + /*** Update profile with api + // format: {type, [(name, result)]} + pub type UpdateProviderType = std::collections::HashMap)>>; + + // Using api update, the user needs to check the logs to understand why the updates failed. The success rate of my testing updates is not as high as using clashtui. + pub fn update_profile_with_api( + &self, + profile_name: &String, + does_update_all: bool, + ) -> std::io::Result> { + + let mut result = Vec::new(); + if self.get_profile_type(profile_name) + .is_some_and(|t| t == ProfileType::Url) + { + let sub_url = self.extract_profile_url(profile_name)?; + let profile_yaml_path = self.get_profile_cache_unchecked(profile_name); + // Update the file to keep up-to-date + self.download_profile(sub_url.as_str(), &profile_yaml_path)?; + + result.push( + format!("Updated: {}, {}", profile_name, sub_url) + ); + } + + let mut provider_types = vec![ProfileSectionType::ProxyProvider]; + if does_update_all { + provider_types.push(ProfileSectionType::RuleProvider); + } + + let mut update_providers_result = UpdateProviderType::new(); + let mut update_times = ProfileTimeMap::new(); + for t in provider_types { + // Get result of update providers + update_providers_result.insert(t, self.clash_api.update_providers(t)?); + + // Get update times after update providers + if let Ok(name_times) = self.clash_api.extract_provider_utimes_with_api(t) { + update_times.insert(t, name_times); + } + } + + // Add results of updating providers + for (section_type, res) in update_providers_result { + for (name, r) in res { + // Generate duration_str + let duration_str = if let Ok(d) = Self::cal_mtime_duration(&update_times, section_type, &name) { + Utils::str_duration(d) + } else { + "No update times or can't cal the duration".to_string() + }; + + let line = match r { + Ok(_) => { + format!("Sent update request: {}, duration = '{}'", name, duration_str) + } + Err(err) => { + log::error!("Not Sent update request:{err}"); + format!("Not Sent update request: {}, duration = '{}'", name, duration_str) + } + }; + result.push(line); + } + } + + Ok(result) + } + + // duration: now - mtime + fn cal_mtime_duration(mtimes: &ProfileTimeMap, section_type: ProfileSectionType, name: &String) -> std::io::Result { + let mt = Self::extract_the_mtime(mtimes, section_type, name).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "No the mtime in mtimes"))?; + let now = std::time::SystemTime::now(); + now.duration_since(mt).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) + } + + fn extract_the_mtime<'a>(mtimes: &'a ProfileTimeMap, section_type: ProfileSectionType, name: &String) -> &'a Option { + if let Some(times) = mtimes.get(§ion_type) { + for (n, the_mtime) in times { + if n == name { + return the_mtime; + } + } + } + + &None + } + + // For Showing provider duration (now - mtime) in `update_profile_with_api`. + // format: {type: [(name, modifytime)]} + pub type ProfileTimeMap = std::collections::HashMap)>>; + fn get_provider_mtime(&self, section_types: Vec, profile_yaml_path: &PathBuf) -> std::io::Result { + let mut modify_info = ProfileTimeMap::new(); + if let Ok(net_res) = self.extract_net_providers(profile_yaml_path, §ion_types) { + for (key, res) in net_res { + let name_and_times = res.into_iter().map(|(name, _, path)| { + let clash_cfg_dir = Path::new(&self.tui_cfg.clash_cfg_dir); + let time = Utils::get_mtime(clash_cfg_dir.join(path)).ok(); + (name, time) + }).collect(); + modify_info.insert(key, name_and_times); + } + } + + Ok(modify_info) + } + ***/ } impl ClashTuiUtil { @@ -656,6 +659,9 @@ impl ClashTuiUtil { } pub fn extract_profile_url(&self, profile_name: &str) -> std::io::Result { + use std::io::BufRead; + use regex::Regex; + let profile_path = self.profile_dir.join(profile_name); let file = File::open(profile_path)?; let reader = std::io::BufReader::new(file); @@ -718,7 +724,7 @@ mod tests { { profile_yaml_path = sym.get_profile_cache_unchecked(profile_name); } - let net_providers = sym.extract_net_providers(&profile_yaml_path, &vec![ProfileSectionType::ProxyProvider]); + let _ = sym.extract_net_providers(&profile_yaml_path, &vec![ProfileSectionType::ProxyProvider]); } } diff --git a/clashtui/src/utils/utils.rs b/clashtui/src/utils/utils.rs index 5ba5f4b..bd18270 100644 --- a/clashtui/src/utils/utils.rs +++ b/clashtui/src/utils/utils.rs @@ -1,9 +1,8 @@ use std::{fs, process, env}; use std::os::unix::fs::{PermissionsExt, MetadataExt}; -use nix::unistd::{Uid, Gid, Group, User, geteuid, setfsuid, setfsgid, getgroups, setgroups, initgroups, setuid, setgid}; -use std::path::{Path, PathBuf}; -//use libc::{getlogin, setreuid, setuid, setgid}; -use std::ffi::{CStr, CString}; +use nix::unistd::{Uid, Gid, Group, geteuid, setfsuid, setfsgid, initgroups}; +use std::path::PathBuf; +use std::ffi::CString; use std::os::unix::process::CommandExt; pub(super) fn get_file_names

(dir: P) -> std::io::Result>