bawa 0.1.0

A TUI game save organizer
Documentation
use super::state;
use crate::entry::Entry;
use crate::tree::Tree;
use crate::utils;
use anyhow::Result;
use std::fmt::Display;
use std::fs;
use std::path::{Path, PathBuf, MAIN_SEPARATOR};

pub struct Profile {
    pub path: PathBuf,
    pub entries: Tree<Entry>,
    pub active_save_file: Option<PathBuf>,
}

impl Profile {
    pub fn new(path: PathBuf) -> Self {
        Self {
            path,
            entries: Tree::default(),
            active_save_file: None,
        }
    }

    pub fn name(&self) -> std::borrow::Cow<'_, str> {
        self.path.file_name().unwrap().to_string_lossy()
    }

    pub fn load_entries(&mut self) -> Result<()> {
        if self.entries.root().is_some() {
            return Ok(());
        }

        let state_file = self.abs_path_to(".state");
        let root = Entry::new(&self.path);
        if let Some(state) = fs::read(state_file)
            .ok()
            .and_then(|s| bincode::deserialize::<state::ProfileState>(&s).ok())
        {
            root.add_to_tree(&state.entries, &mut self.entries)?;
            self.active_save_file = state.active_save_file.map(|rel| self.abs_path_to(rel));
        } else {
            root.add_to_tree(&[], &mut self.entries)?;
        }

        Ok(())
    }

    pub fn write_state(&self) -> Result<()> {
        utils::write_atomic(&self.abs_path_to(".state"), &bincode::serialize(self)?)
    }

    pub fn get_active_save_file(&self) -> Option<PathBuf> {
        self.active_save_file.clone()
    }

    pub fn update_active_save_file(&mut self, path: &Path) -> Result<()> {
        if matches!(&self.active_save_file, Some(active_path) if active_path == path) {
            return Ok(());
        }

        self.active_save_file = Some(path.to_owned());
        self.write_state()?;

        Ok(())
    }

    pub fn reset_active_save_file(&mut self) -> Result<()> {
        self.active_save_file = None;
        self.write_state()?;

        Ok(())
    }

    pub fn abs_path_to<A: AsRef<Path>>(&self, path: A) -> PathBuf {
        self.path.join(path)
    }

    pub fn rel_path_to(&self, entry_path: &Path) -> String {
        utils::get_relative_path(&self.path, entry_path)
            .unwrap()
            .to_string_lossy()
            .to_string()
    }

    pub fn get_file_rel_paths(&self, include_folders: bool) -> Vec<String> {
        let mut paths = Vec::new();

        for entry in self.entries.iter_nodes().skip(1) {
            if include_folders || entry.is_file() {
                let mut path = self.rel_path_to(&entry.path);

                if entry.is_folder() {
                    path.push(MAIN_SEPARATOR);
                }

                paths.push(path);
            }
        }

        paths
    }
}

impl Display for Profile {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.name())
    }
}