diff --git a/src/app/session.rs b/src/app/session.rs index 1a058a7..12f051a 100644 --- a/src/app/session.rs +++ b/src/app/session.rs @@ -1,3 +1,4 @@ +use std::fs; use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -19,6 +20,7 @@ pub const DEFAULT_WORD_WRAP_ENABLED: bool = true; pub const DEFAULT_ALWAYS_ON_TOP: bool = false; const APP_DIR_NAME: &str = "Rivet"; +const PORTABLE_DATA_DIR_NAME: &str = "data"; const SESSIONS_DIR_NAME: &str = "sessions"; const BACKUP_DIR_NAME: &str = "backup"; const SESSION_FILE_NAME: &str = "session.json"; @@ -198,7 +200,33 @@ pub fn decide_restore_source(input: &RestoreDecisionInput) -> RestoreSource { } } +/// Data directory next to the executable, used when that location is +/// writable (i.e. running as a portable build, not installed under a +/// protected directory like Program Files). Returns `None` if the exe path +/// can't be resolved or the directory isn't writable, in which case the +/// caller falls back to the per-user AppData location. +fn portable_data_dir() -> Option { + if cfg!(test) { + // Tests exercise data_dir() via LOCALAPPDATA/APPDATA env overrides; + // the test binary's own directory is writable, which would + // short-circuit those overrides and scribble into target/. + return None; + } + let exe = std::env::current_exe().ok()?; + let exe_dir = exe.parent()?; + let candidate = exe_dir.join(PORTABLE_DATA_DIR_NAME); + fs::create_dir_all(&candidate).ok()?; + let probe = candidate.join(".write_test"); + fs::write(&probe, b"").ok()?; + let _ = fs::remove_file(&probe); + Some(candidate) +} + pub fn data_dir() -> Result { + if let Some(portable) = portable_data_dir() { + return Ok(portable); + } + if let Ok(local) = std::env::var("LOCALAPPDATA") && !local.is_empty() { diff --git a/src/logging.rs b/src/logging.rs index 074dfa4..63e8e53 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -4,10 +4,7 @@ use std::path::{Path, PathBuf}; use std::sync::{Mutex, OnceLock}; use std::time::{SystemTime, UNIX_EPOCH}; -use windows::Win32::Foundation::HANDLE; -use windows::Win32::System::Com::CoTaskMemFree; -use windows::Win32::UI::Shell::{FOLDERID_LocalAppData, KNOWN_FOLDER_FLAG, SHGetKnownFolderPath}; - +use crate::app::session; use crate::error::{AppError, Result}; const MAX_LOG_SIZE: u64 = 512 * 1024; @@ -81,26 +78,8 @@ fn open_log_file() -> Result { } fn log_directory() -> Result { - let path = local_appdata_path().unwrap_or_else(std::env::temp_dir); - Ok(path.join("Rivet").join("logs")) -} - -fn local_appdata_path() -> Option { - let path = - unsafe { SHGetKnownFolderPath(&FOLDERID_LocalAppData, KNOWN_FOLDER_FLAG(0), HANDLE(0)) } - .ok()?; - let buffer = unsafe { - let mut length = 0usize; - while *path.0.add(length) != 0 { - length += 1; - } - std::slice::from_raw_parts(path.0, length) - }; - let value = String::from_utf16(buffer).ok().map(PathBuf::from); - unsafe { - CoTaskMemFree(Some(path.0 as _)); - } - value + let path = session::data_dir().unwrap_or_else(|_| std::env::temp_dir().join("Rivet")); + Ok(path.join("logs")) } fn rotate_logs(path: &Path) -> Result<()> {