ct-tracker-lib 0.1.1

A simple library for time tracking.
Documentation
use super::super::ct_fs;
use super::errors;
use super::has as proj_exists;
use super::ProjectFrame;
use std::{
    fs::{File, OpenOptions},
    io::{Read, Write},
    path::PathBuf,
};

const PERSISTENT_FILE_NAME: &str = "./stored.ctstore";

// TODO separate stores -> v0.2.0 """maybe"""
//      -> One for the running project
//      -> One for the last used project

fn store_path() -> errors::CtResult<PathBuf> {
    Ok(ct_fs::default_path::conf_path()?.join(PERSISTENT_FILE_NAME))
}

fn clear_store_and_get_handle() -> errors::CtResult<File> {
    OpenOptions::new()
        .write(true)
        .truncate(true)
        .create(true)
        .open(store_path()?)
        .map_err(|e| e.into())
}

pub(crate) fn clear_store() -> errors::CtResult<()> {
    clear_store_and_get_handle()?;
    Ok(())
}

/// Is there a project running currently?
/// Meaning, is it stored as a reference in the persistent store?
/// If one exists, the project name is returned
/// This already checks, if the project stored in the persistent store exists
pub fn has_project() -> errors::CtResult<Option<String>> {
    let path = store_path()?;
    let mut file = if !path.exists() {
        OpenOptions::new()
            .write(true)
            .read(true)
            .create_new(true)
            .open(path)?;
        // File has been created, so we don't need to check if something is in it
        return Ok(None);
    } else {
        OpenOptions::new().read(true).open(path)?
    };
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;

    Ok(if let Some(line) = contents.lines().next() {
        // We only care about the first line
        if line.is_empty() {
            // File is empty
            None
        } else if proj_exists(line)? {
            Some(line.to_owned())
        } else {
            // Just clear the store file, as the project in it doesn't exist.
            clear_store()?;
            None
        }
    } else {
        None
    })
}

pub(crate) fn register(name: &str) -> errors::CtResult<()> {
    if let Some(stored) = has_project()? {
        if name == stored {
            return Ok(());
        }
    }

    let mut file = clear_store_and_get_handle()?;
    file.write_all(name.as_bytes())?;
    Ok(())
}

/// Deregister a project from the persistent store
/// If you pass Some(name), it will only deregister that project,
/// otherwise it will deregister any project
pub(crate) fn deregister(name: Option<&str>) -> errors::CtResult<()> {
    if let Some(name) = name {
        if let Some(stored) = has_project()? {
            if stored != name {
                return Ok(());
            }
        } else {
            return Ok(());
        }
    }
    clear_store()?;
    Ok(())
}

/// Validate the persistent store -> Check if files have been deleted, if project exists.
/// Check if project stored as running is actually running.
pub(crate) fn validate() -> errors::CtResult<()> {
    if let Some(name) = has_project()? {
        let p = ProjectFrame::load_from_name(name.as_str())?;
        if !p.is_open() {
            // The project in the persistent store is not running, so we
            // clear the store to make it accurate again
            clear_store()?;
        }
    };
    Ok(())
}

pub(crate) fn delete_stored_if(name: &str) -> errors::CtResult<bool> {
    if let Some(proj) = has_project()? {
        if proj == name {
            clear_store()?;
            return Ok(true);
        }
    }
    Ok(false)
}