use std::path::{Path, PathBuf}; use std::sync::Mutex; pub const RON_EXTENSION: &'static str = "ron"; pub const JSON_EXTENSION: &'static str = "json"; const SETTING_FOLDER: &'static str = "settings"; const ID_FOLDER: &'static str = "by_id"; const APP_ID_FOLDER: &'static str = "by_app_id"; const USER_ID_FOLDER: &'static str = "by_user_id"; const TAG_FOLDER: &'static str = "by_tag"; static LAST_SETTING_ID: Mutex = Mutex::new(0); pub fn build_folder_layout(root: impl AsRef) -> std::io::Result<()> { std::fs::create_dir_all( root.as_ref() .join(SETTING_FOLDER) .join(ID_FOLDER) )?; std::fs::create_dir_all( root.as_ref() .join(SETTING_FOLDER) .join(APP_ID_FOLDER) )?; std::fs::create_dir_all( root.as_ref() .join(SETTING_FOLDER) .join(USER_ID_FOLDER) )?; std::fs::create_dir_all( root.as_ref() .join(SETTING_FOLDER) .join(TAG_FOLDER) )?; Ok(()) } pub fn fix_symlinks(root: impl AsRef) -> std::io::Result<()> { log::info!("root setttings folder: {} aka {} (absolute)", root.as_ref().display(), root.as_ref().canonicalize()?.display()); for dir_entry in root.as_ref() .join(SETTING_FOLDER) .join(APP_ID_FOLDER) .read_dir()? { let dir_entry = dir_entry?; if dir_entry.file_type()?.is_dir() { make_symlinks_absolute_in_dir(root.as_ref(), dir_entry.path())?; } } for dir_entry in root.as_ref() .join(SETTING_FOLDER) .join(USER_ID_FOLDER) .read_dir()? { let dir_entry = dir_entry?; if dir_entry.file_type()?.is_dir() { make_symlinks_absolute_in_dir(root.as_ref(), dir_entry.path())?; } } for dir_entry in root.as_ref() .join(SETTING_FOLDER) .join(TAG_FOLDER).read_dir()? { let dir_entry = dir_entry?; if dir_entry.file_type()?.is_dir() { make_symlinks_absolute_in_dir(root.as_ref(), dir_entry.path())?; } } Ok(()) } fn make_symlinks_absolute_in_dir(root: impl AsRef, dir: impl AsRef) -> std::io::Result<()> { let abs_root = root.as_ref().canonicalize()?; assert!(abs_root.is_absolute()); for dir_entry in dir.as_ref() .read_dir()? { let dir_entry = dir_entry?; if dir_entry.file_type()?.is_symlink() { let path = dir_entry.path(); let link_path = path.read_link()?; if !link_path.is_absolute() { let new_link = abs_root.join( link_path.strip_prefix(&root).expect("Symlinked path does not begin with root settings folder") ); log::info!("Fixing {} -> {} to -> {}", path.display(), link_path.display(), new_link.display()); std::fs::remove_file(&path)?; #[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained { std::os::windows::fs::symlink_file(new_link, &path)?; } #[cfg(target_family = "unix")] { std::os::unix::fs::symlink(new_link, &path)?; } }else { log::info!("Found already-absolute symlink {} -> {}", path.display(), link_path.display()); } } else { log::info!("Found non-symlink {}: {:?}", dir_entry.path().display(), dir_entry.file_type()?); } } Ok(()) } pub fn filename(id: u128, ext: &str) -> String { format!("{}.{}", id, ext) } pub fn setting_path_by_id(root: impl AsRef, id: u128, ext: &str) -> PathBuf { root.as_ref() .join(SETTING_FOLDER) .join(ID_FOLDER) .join(filename(id, ext)) } pub fn setting_folder_by_app_id(root: impl AsRef, steam_app_id: u32) -> PathBuf { root.as_ref() .join(SETTING_FOLDER) .join(APP_ID_FOLDER) .join(steam_app_id.to_string()) } pub fn setting_folder_by_user_id(root: impl AsRef, steam_user_id: u64) -> PathBuf { root.as_ref() .join(SETTING_FOLDER) .join(USER_ID_FOLDER) .join(steam_user_id.to_string()) } pub fn setting_folder_by_tag(root: impl AsRef, tag: &str) -> PathBuf { root.as_ref() .join(SETTING_FOLDER) .join(TAG_FOLDER) .join(tag) } pub fn next_setting_id(root: impl AsRef) -> u128 { let mut lock = LAST_SETTING_ID.lock().unwrap(); let mut last_id = *lock; if last_id == 0 { // needs init last_id = 1; let mut path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION); while path.exists() { last_id += 1; path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION); } *lock = last_id - 1; log::info!("setting id initialized to {}", last_id); } *lock += 1; *lock }