oliver 0.1.2

Lightweight CLI mod manager for Baldur's Gate 3 on Linux
use anyhow::Result;
use larian_formats::bg3::Save;
use std::{
    borrow::Cow,
    fs::{File, OpenOptions},
    io::{BufReader, Write},
    os::unix::fs::PermissionsExt,
    path::{Path, PathBuf},
    sync::OnceLock,
};

pub const DEFAULT_APP_DATA_DIR: &str =
    "~/.local/share/Steam/steamapps/compatdata/1086940/pfx/drive_c/users/steamuser/AppData";

static APP_DATA_DIR: OnceLock<String> = OnceLock::new();
static MODS_PATH: OnceLock<PathBuf> = OnceLock::new();
static SETTINGS_PATH: OnceLock<PathBuf> = OnceLock::new();

pub fn open_mod_settings_file(app_data_path: &str) -> Result<File> {
    let file = File::open(settings_file_path(app_data_path))?;
    Ok(file)
}

pub fn read_current_installed_mods(app_data_path: &str) -> Result<Save> {
    let settings_file = open_mod_settings_file(app_data_path)?;

    let parsed_settings = quick_xml::de::from_reader(BufReader::new(settings_file))?;
    Ok(parsed_settings)
}

pub fn write_file(
    path: impl AsRef<Path>,
    contents: impl AsRef<[u8]>,
    is_readonly: bool,
) -> Result<()> {
    if is_readonly {
        let file = File::open(path.as_ref())?;
        let mut permissions = file.metadata()?.permissions();
        permissions.set_mode(permissions.mode() | 0o600);
        file.set_permissions(permissions)?;
    }

    let mut file = OpenOptions::new()
        .write(true)
        .truncate(true)
        .create(true)
        .open(&path)?;

    file.write_all(contents.as_ref())?;

    if is_readonly {
        let mut permissions = file.metadata()?.permissions();
        permissions.set_readonly(true);
        file.set_permissions(permissions)?;
    }

    Ok(())
}

pub fn app_data_dir(app_data_path: &str) -> &'static str {
    APP_DATA_DIR.get_or_init(|| {
        let app_data_path = match std::env::var("HOME") {
            Ok(home_dir) if app_data_path.starts_with('~') => {
                Cow::Owned(app_data_path.replacen('~', &home_dir, 1))
            }
            _ => Cow::Borrowed(app_data_path),
        };

        app_data_path.into_owned()
    })
}

pub fn mods_directory_path(app_data_path: &str) -> &'static PathBuf {
    MODS_PATH.get_or_init(|| {
        [
            app_data_dir(app_data_path),
            "Local",
            "Larian Studios",
            "Baldur's Gate 3",
            "Mods",
        ]
        .into_iter()
        .collect()
    })
}

pub fn settings_file_path(app_data_path: &str) -> &'static PathBuf {
    SETTINGS_PATH.get_or_init(|| {
        [
            app_data_dir(app_data_path),
            "Local",
            "Larian Studios",
            "Baldur's Gate 3",
            "PlayerProfiles",
            "Public",
            "modsettings.lsx",
        ]
        .into_iter()
        .collect()
    })
}