creeper 1.0.0-alpha.10

Minecraft Package Manager
use std::{
    path::PathBuf,
    time::{Duration, SystemTime, UNIX_EPOCH},
};

use anyhow::bail;
use tokio::fs::{create_dir_all, read_to_string, try_exists, write};
use tracing::debug;

use crate::{
    Id,
    index::{Index, IndexLine},
    path::creeper_cache_dir,
};

pub trait SyncBuiltinIndex {
    fn package(&self) -> Id;

    async fn sync_index(&self) -> anyhow::Result<Index>;

    fn cache_expiry(&self) -> Duration {
        Duration::from_secs(0)
    }
}

pub trait GetIndex {
    async fn get_index(&self) -> anyhow::Result<Index>;
}

pub trait BlockingGetIndex {
    fn blocking_get_index(&self) -> anyhow::Result<Index>;
}

pub trait UpdateIndex {
    async fn update_index(&self) -> anyhow::Result<()>;
}

impl<T: SyncBuiltinIndex> UpdateIndex for T {
    async fn update_index(&self) -> anyhow::Result<()> {
        let package = self.package();

        let cache = builtin_index_cache(&package)?;

        let last_updated = creeper_cache_dir()?
            .join("builtin")
            .join(&*package)
            .join("index-last-updated");

        if try_exists(&cache).await?
            && self.get_index().await.is_ok()
            && try_exists(&last_updated).await?
        {
            let last_updated = read_to_string(&last_updated).await?.parse::<u64>()?;
            let last_updated = SystemTime::UNIX_EPOCH + Duration::from_secs(last_updated);

            let now = SystemTime::now();

            if let Ok(time) = now.duration_since(last_updated)
                && time < self.cache_expiry()
            {
                debug!("skipping builtin index update for {package} because cache is still valid");
                return Ok(());
            }
        }

        let index = self.sync_index().await?;

        IndexLine::write(cache, &package, index).await?;

        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();

        create_dir_all(last_updated.parent().unwrap()).await?;

        write(&last_updated, now.to_string()).await?;

        Ok(())
    }
}

fn builtin_index_cache(package: &Id) -> anyhow::Result<PathBuf> {
    let path = creeper_cache_dir()?
        .join("index")
        .join(package.indexed_path())
        .with_added_extension("jsonl");

    Ok(path)
}

impl<T: SyncBuiltinIndex> GetIndex for T {
    async fn get_index(&self) -> anyhow::Result<Index> {
        let package = self.package();

        let cache = builtin_index_cache(&package)?;

        if !cache.exists() {
            bail!("missing package index for {} in local cache", package);
        }

        let index = IndexLine::read(cache).await?;

        Ok(index)
    }
}

impl<T: SyncBuiltinIndex> BlockingGetIndex for T {
    fn blocking_get_index(&self) -> anyhow::Result<Index> {
        let package = self.package();

        let cache = builtin_index_cache(&package)?;

        if !cache.exists() {
            bail!("missing package index for {} in local cache", package);
        }

        let index = IndexLine::blocking_read(cache)?;

        Ok(index)
    }
}