mercur 0.1.5

mercur - junolang package manager
use anyhow::Error;
use directories::ProjectDirs;
use sha2::{Digest, Sha256};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use tokio::time::Instant;

use crate::registry::schema::registry::{Registry, root_as_registry};
use crate::registry::search::{LoadedRegistry, SearchEngine};
use std::fs;
use std::path::PathBuf;
use std::sync::Arc;

impl SearchEngine {
    fn get_tmp_path_for_ref(&self, reference: &str) -> anyhow::Result<PathBuf> {
        let proj_dirs = ProjectDirs::from("dev", "mercurpkg", "mercur")
            .expect("could not determine cache directory");

        let mut hasher = Sha256::new();
        hasher.update(reference.as_bytes());
        let hash = hex::encode(hasher.finalize());

        let mut path = proj_dirs.cache_dir().to_path_buf();
        path.push("registries");

        fs::create_dir_all(&path)?;

        path.push(format!("{hash}.mreg"));

        Ok(path)
    }
    pub(super) async fn load_registry(&mut self, reference: &str) -> anyhow::Result<Registry<'_>> {
        if self.registries.get(reference).is_none() {
            let tmp_path = self.get_tmp_path_for_ref(reference)?;

            if !tmp_path.exists() {
                self.download_registry(&reference).await?;
            }

            let data = std::fs::read(&tmp_path)?;

            self.registries.insert(
                Arc::from(reference),
                LoadedRegistry {
                    data: Arc::new(data),
                    last_used: Instant::now(),
                },
            );
        }

        let loaded = self
            .registries
            .get(reference)
            .ok_or_else(|| anyhow::anyhow!("Registry not found in cache"))?;

        Ok(root_as_registry(&loaded.data)?)
    }

    pub async fn download_registry(&self, reference: &str) -> anyhow::Result<Vec<u8>> {
        let url = &self.resolve_registry_url(reference)?;
        println!("DOWNLOADING REGISTRY ref'{}'", url);
        let response = reqwest::get(url).await?;

        if !response.status().is_success() {
            anyhow::bail!(
                "Failed to download registry '{}': {}",
                url,
                response.status()
            );
        }

        let mut bytes = response.bytes().await?;
        let tmp_path = self.get_tmp_path_for_ref(reference)?;
        let tmp_path = tmp_path.as_path();
        println!(
            "SAVING REGISTRY IN '{}'",
            &tmp_path.to_str().ok_or(anyhow::anyhow!("UTF-8 Error"))?
        );
        File::create(tmp_path).await?.write_buf(&mut bytes).await?;
        Ok(bytes.to_vec())
    }

    pub fn resolve_registry_url(&self, reference: &str) -> anyhow::Result<String> {
        if let Some(rest) = reference.strip_prefix("github:") {
            let mut rest = rest;
            let mut branch = "main";
            let s: Vec<&str> = rest.split("#").collect();
            if s.len() == 2 {
                rest = s[0];
                branch = s[1];
            }
            Ok(format!(
                "https://raw.githubusercontent.com/{rest}/{branch}/registry.mreg"
            ))
        } else if reference.starts_with("http://") || reference.starts_with("https://") {
            Ok(reference.to_string())
        } else {
            Err(Error::msg(format!(
                "can not parse reference '{}'",
                reference
            )))
        }
    }
}