From 3aa5984a9ef2538b64a7a8ece36c5c97dea7f481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E4=BB=A5=E7=90=B3?= Date: Tue, 16 Jun 2026 04:02:02 +0800 Subject: [PATCH] fix(config): choose macos db dir by db mtimes --- src/config.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/src/config.rs b/src/config.rs index ed91059..192a318 100644 --- a/src/config.rs +++ b/src/config.rs @@ -233,6 +233,11 @@ fn detect_db_dir_impl() -> Option { let home = sudo_user_home_dir().or_else(dirs::home_dir)?; let base = home.join("Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files"); + detect_db_dir_under(&base) +} + +#[cfg(target_os = "macos")] +fn detect_db_dir_under(base: &Path) -> Option { if !base.exists() { return None; } @@ -245,11 +250,7 @@ fn detect_db_dir_impl() -> Option { } } } - candidates.sort_by_key(|p| { - std::fs::metadata(p) - .and_then(|m| m.modified()) - .unwrap_or(std::time::SystemTime::UNIX_EPOCH) - }); + candidates.sort_by_key(|p| latest_db_mtime(p).unwrap_or(std::time::SystemTime::UNIX_EPOCH)); candidates.into_iter().next_back() } @@ -284,7 +285,7 @@ fn detect_db_dir_impl() -> Option { candidates.into_iter().next_back() } -#[cfg(any(target_os = "linux", target_os = "windows"))] +#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))] /// 递归查找 db_storage 目录下所有 .db 文件的最新 mtime fn latest_db_mtime(dir: &Path) -> Option { let mut latest = None; @@ -416,8 +417,8 @@ fn detect_db_dir_impl() -> Option { #[cfg(test)] mod tests { use super::{ - config_path_in_dir, default_config_path, find_existing_config_path, home_config_path, - resolve_cli_home, + config_path_in_dir, default_config_path, detect_db_dir_under, find_existing_config_path, + home_config_path, resolve_cli_home, }; #[cfg(target_os = "windows")] use super::{known_documents_dir, resolve_windows_data_root}; @@ -480,6 +481,34 @@ mod tests { assert_eq!(path, cwd.join("config.json")); } + #[cfg(target_os = "macos")] + #[test] + fn auto_detect_db_dir_prefers_latest_db_file_mtime_over_directory_mtime() { + let home = temp_dir("macos-home"); + + let base = home.join("Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files"); + let active_db_dir = base.join("wxid_active/db_storage"); + let stale_db_dir = base.join("wxid_stale/db_storage"); + let active_db_file = active_db_dir.join("message/session.db"); + let stale_db_file = stale_db_dir.join("message/session.db"); + + fs::create_dir_all(active_db_file.parent().unwrap()).unwrap(); + fs::write(&active_db_file, b"active").unwrap(); + fs::create_dir_all(stale_db_file.parent().unwrap()).unwrap(); + fs::write(&stale_db_file, b"stale").unwrap(); + set_file_mtime( + &active_db_file, + SystemTime::now() + .checked_add(std::time::Duration::from_secs(3600)) + .unwrap(), + ); + + let detected = detect_db_dir_under(&base).unwrap(); + assert_eq!(detected, active_db_dir); + + fs::remove_dir_all(home).unwrap(); + } + #[cfg(target_os = "windows")] #[test] fn resolve_windows_data_root_passes_through_absolute_path() { @@ -499,4 +528,28 @@ mod tests { assert_eq!(resolved, docs, "keyword {keyword:?}"); } } + + #[cfg(unix)] + fn set_file_mtime(path: &std::path::Path, mtime: SystemTime) { + use std::ffi::CString; + use std::os::unix::ffi::OsStrExt; + use std::time::Duration; + + let dur = mtime + .duration_since(UNIX_EPOCH) + .unwrap_or(Duration::from_secs(0)); + let times = [ + libc::timeval { + tv_sec: dur.as_secs() as libc::time_t, + tv_usec: dur.subsec_micros() as libc::suseconds_t, + }, + libc::timeval { + tv_sec: dur.as_secs() as libc::time_t, + tv_usec: dur.subsec_micros() as libc::suseconds_t, + }, + ]; + let c_path = CString::new(path.as_os_str().as_bytes()).unwrap(); + let rc = unsafe { libc::utimes(c_path.as_ptr(), times.as_ptr()) }; + assert_eq!(rc, 0, "failed to update mtime for {}", path.display()); + } }