please-install 1.2.0

A unified interface package manager for many OSes
Documentation
use std::{
    collections::BTreeSet,
    env,
    fs::{self, File},
    io::{Read, Write},
    path::PathBuf,
    sync::LazyLock,
};

use eyre::{Result, eyre};

use crate::package::{Package, PackageFile};
use crate::vendors::Vendor;

pub fn save_installed_packages(packages: Vec<Package>, fix_revendor: bool) -> Result<()> {
    let mut tracks: BTreeSet<Package> = load_tracks()
        .unwrap_or_default()
        .into_iter()
        .collect();
    for mut package in packages {
        if fix_revendor {
            revendor_package(&mut package)?;
        }
        tracks.insert(package);
    }
    let tracks: Vec<Package> = tracks.into_iter().collect();
    save_tracks(tracks)?;
    Ok(())
}

pub fn remove_installed_packages(packages: Vec<Package>) -> Result<()> {
    let mut tracks = load_tracks().unwrap_or_default();
    if tracks.is_empty() {
        return Ok(());
    }
    for package in &packages {
        tracks.retain(|track| *track != *package);
    }
    save_tracks(tracks)?;
    Ok(())
}

pub fn load_tracks() -> Result<Vec<Package>> {
    let mut file = File::open(match TRACK.to_str() {
        Some(path) => path,
        _ => return Err(eyre!("package track file (packages.toml) not found")),
    }).map_err(|_| eyre!("error opening package track file (packages.toml)"))?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    let data: PackageFile = toml::from_str(&content)?;
    Ok(data.installed)
}

fn save_tracks(tracks: Vec<Package>) -> Result<()> {
    let data = PackageFile { installed: tracks };
    let content = toml::to_string(&data)?;
    let mut file = File::create(match TRACK.to_str() {
        Some(path) => path,
        _ => return Err(eyre!("error creating track file")),
    })?;
    file.write_all(content.as_bytes())?;
    Ok(())
}

fn revendor_package(package: &mut Package) -> Result<()> {
    match package.vendor {
        Vendor::Pacman |
        Vendor::Rua => {
            if Vendor::Yay.is_available()? {
                package.vendor = Vendor::Yay;
            } else if Vendor::Rua.is_available()? {
                package.vendor = Vendor::Rua;
            }
        }

        _ => (),
    }

    Ok(())
}

static TRACK: LazyLock<PathBuf> = LazyLock::new(|| PLEASE_DATA.join("packages.toml"));
static PLEASE_DATA: LazyLock<PathBuf> = LazyLock::new(|| {
    let data = DATA_HOME.join("cc.cacilhas.please");
    if !data.exists() {
        let old = DATA_HOME.join("info.cacilhas.please");
        if old.exists() {
            let _ = fs::rename(&old, &data);
        } else {
            let _ = fs::create_dir_all(&data);
        }
    }
    data
});

#[cfg(not(any(target_os = "macos", target_os = "windows")))]
static DATA_HOME: LazyLock<PathBuf> = LazyLock::new(||
    env::var("XDG_DATA_HOME")
        .map(|dir| PathBuf::from(dir))
        .unwrap_or_else(|_|
            PathBuf::from(env!["HOME"])
                .join(".local")
                .join("share")
    )
);

#[cfg(target_os = "macos")]
static DATA_HOME: LazyLock<PathBuf> = LazyLock::new(||
    env::var("XDG_DATA_HOME")
        .map(|dir| PathBuf::from(dir))
        .unwrap_or_else(|_|
            PathBuf::from(env!["HOME"])
                .join("Library")
                .join("Application Support")
    )
);

#[cfg(target_os = "windows")]
static DATA_HOME: LazyLock<String> = LazyLock::new(||
    env::var("APPDATA")
        .map(|dir| PathBuf::from(dir))
        .unwrap_or_else(|_|
            PathBuf::from(env!["HOMEPATH"])
                .join("AppData")
                .join("Roaming")
    )
);