aoc 0.6.1

Advent of code helpers
Documentation
use std::fs::{self, File};
use std::io::ErrorKind::NotFound;
use std::path::PathBuf;

use anyhow::Context;
use chrono::prelude::*;
use directories::ProjectDirs;
use serde_json as json;

use crate::{Leaderboard, Result};

#[derive(Serialize, Deserialize, Default)]
struct Config {
    leaderboard_url: Option<String>,
    session_token: String,
    last_api_access: Option<DateTime<Local>>,
    last_api_response: Option<Leaderboard>,
}

impl Config {
    fn load() -> Result<Self> {
        let path = config_path()?;
        let file = match File::open(path) {
            Ok(file) => file,
            Err(ref err) if err.kind() == NotFound => return Ok(Self::default()),
            Err(err) => return Err(err.into()),
        };
        let config = json::from_reader(file)?;

        Ok(config)
    }

    fn save(&self) -> Result<()> {
        let path = config_path()?;
        let file = File::create(path)?;
        json::to_writer_pretty(file, self)?;
        Ok(())
    }
}

fn config_path() -> Result<PathBuf> {
    let dirs = ProjectDirs::from("", "panicbit", "advent_of_code")
        .context("Failed to find config folder")?;
    let dir = dirs.config_dir();

    fs::create_dir_all(dir)?;

    Ok(dir.join("config"))
}

pub fn leaderboard_url() -> Result<String> {
    let config = Config::load()?;
    let url = config.leaderboard_url.context(
        "Leaderboard url not set.\n\
        Set one using `--url https://adventofcode.com/YEAR/leaderboard/private/view/ID`.\n\
        You can get this URL by viewing your private leaderboard\n\
        and copying it from your browser's address bar.",
    )?;
    Ok(url)
}

pub fn set_leaderboard_url<U: Into<String>>(url: U) -> Result<()> {
    let mut config = Config::load()?;
    let mut url = url.into();

    if !url.ends_with(".json") {
        url += ".json";
    }

    config.leaderboard_url = Some(url);
    config.save().context("Failed to save leaderboard URL")?;

    Ok(())
}

pub fn session_token() -> Result<String> {
    let config = Config::load()?;
    let token = config.session_token;
    Ok(token)
}

pub fn set_session_token<S: Into<String>>(token: S) -> Result<()> {
    let mut config = Config::load()?;

    config.session_token = token.into();
    config.save().context("Failed to save session token")?;

    Ok(())
}

pub fn last_api_access() -> Result<Option<DateTime<Local>>> {
    let config = Config::load()?;
    Ok(config.last_api_access)
}

pub fn set_last_api_access(last_access: Option<DateTime<Local>>) -> Result<()> {
    let mut config = Config::load()?;

    config.last_api_access = last_access;
    config
        .save()
        .context("Failed to save last API access timestamp")?;

    Ok(())
}

pub fn last_leaderboard() -> Result<Option<Leaderboard>> {
    let config = Config::load()?;
    Ok(config.last_api_response)
}

pub fn set_last_leaderboard(leaderboard: Leaderboard) -> Result<()> {
    let mut config = Config::load()?;

    config.last_api_response = Some(leaderboard);
    config.save().context("Failed to save last leaderboard")?;

    Ok(())
}