owlgo 0.1.7

A lightweight CLI to assist in solving CP problems
use crate::common::{OwlError, Result};
use crate::owl_utils::{Uri, fs_utils, toml_utils};
use crate::{MANIFEST, OWL_DIR, PROMPT_DIR, STASH_DIR, TMP_ARCHIVE};
use futures::prelude::*;
use std::path::Path;

pub async fn fetch_extension(ext_name: &str) -> Result<()> {
    let manifest_path = fs_utils::ensure_path_from_home(&[OWL_DIR], Some(MANIFEST))?;

    if !manifest_path.exists() {
        return Err(OwlError::FileError(
            "The manifest does not exist".into(),
            "".into(),
        ));
    }

    let manifest_doc = toml_utils::read_toml(&manifest_path)?;

    let uri = match manifest_doc["ext_uri"].get(ext_name) {
        Some(uri_item) => {
            let uri_str = uri_item.as_str().ok_or(OwlError::TomlError(
                format!("Invalid URI entry '{}' in manifest", ext_name),
                "None".into(),
            ))?;
            Uri::try_from(uri_str)?
        }
        None => {
            return Err(OwlError::TomlError(
                format!("'{}': no such entry found manifest", ext_name),
                "None".into(),
            ));
        }
    };

    let ext_doc = match uri {
        Uri::Local(path) => {
            eprintln!(
                "reading extension '{}' at '{}'",
                ext_name,
                path.to_string_lossy()
            );
            toml_utils::read_toml(&path)?
        }
        Uri::Remote(url) => {
            eprintln!(">>> requesting extension '{}' from '{}' ...", ext_name, url);
            toml_utils::request_toml(&url).await?
        }
    };

    let owl_path = manifest_path.parent().expect("owlgo directory to exist");

    let tmp_archive = Path::new(TMP_ARCHIVE);

    let quest_futures = ext_doc["quests"]
        .as_table()
        .into_iter()
        .flat_map(|quests_table| quests_table.iter())
        .map(|(quest_name, quest_uri)| async move {
            let mut quest_path = owl_path.to_path_buf();
            quest_path.push(quest_name);

            let quest_uri_str = quest_uri.as_str().ok_or(OwlError::TomlError(
                format!("Invalid entry '{}' in extension '{}'", quest_name, ext_name),
                "None".into(),
            ))?;

            match Uri::try_from(quest_uri_str)? {
                Uri::Local(path) => {
                    eprintln!(
                        ">>> extracting quest '{}' at '{}' ...",
                        quest_name,
                        path.to_string_lossy()
                    );
                    fs_utils::extract_archive(&path, &quest_path, false).await
                }
                Uri::Remote(url) => {
                    eprintln!(">>> downloading quest '{}' from '{}' ...", quest_name, url);
                    fs_utils::download_archive(&url, tmp_archive, &quest_path).await
                }
            }
        });

    let prompt_futures = ext_doc["prompts"]
        .as_table()
        .into_iter()
        .flat_map(|prompts_table| prompts_table.iter())
        .map(|(prompt_name, prompt_uri)| async move {
            let mut prompt_path = owl_path.to_path_buf();
            prompt_path.push(STASH_DIR);
            prompt_path.push(PROMPT_DIR);
            prompt_path.push(prompt_name);

            let prompt_uri_str = prompt_uri.as_str().ok_or(OwlError::TomlError(
                format!(
                    "Invalid entry '{}' in extension '{}'",
                    prompt_name, ext_name
                ),
                "None".into(),
            ))?;

            match Uri::try_from(prompt_uri_str)? {
                Uri::Local(path) => {
                    eprintln!(
                        ">>> copying prompt '{}' from '{}' ...",
                        prompt_name,
                        path.to_string_lossy()
                    );
                    fs_utils::copy_file_async(&path, &prompt_path).await
                }
                Uri::Remote(url) => {
                    eprintln!(
                        ">>> downloading prompt '{}' from '{}' ...",
                        prompt_name, url
                    );
                    fs_utils::download_file(&url, &prompt_path).await
                }
            }
        });

    let quest_stream = futures::stream::iter(quest_futures).buffer_unordered(8);
    let prompt_stream = futures::stream::iter(prompt_futures).buffer_unordered(8);

    for result in quest_stream.collect::<Vec<_>>().await {
        result?;
    }

    for result in prompt_stream.collect::<Vec<_>>().await {
        result?;
    }

    Ok(())
}

pub async fn fetch_prompt(prompt_name: &str) -> Result<()> {
    let manifest_path = fs_utils::ensure_path_from_home(&[OWL_DIR], Some(MANIFEST))?;
    let prompt_path =
        fs_utils::ensure_path_from_home(&[OWL_DIR, STASH_DIR, PROMPT_DIR], Some(prompt_name))?;

    if !manifest_path.exists() {
        return Err(OwlError::FileError(
            "The manifest does not exist".into(),
            "".into(),
        ));
    }

    let manifest_doc = toml_utils::read_toml(&manifest_path)?;

    let prompt_entry = manifest_doc["personal_prompts"]
        .get(prompt_name)
        .or(manifest_doc["prompts"].get(prompt_name));

    let uri = match prompt_entry {
        Some(uri_item) => {
            let uri_str = uri_item.as_str().ok_or(OwlError::TomlError(
                format!("Invalid entry '{}' in manifest", prompt_name),
                "None".into(),
            ))?;
            Uri::try_from(uri_str)?
        }
        None => {
            return Err(OwlError::TomlError(
                format!("'{}': no such entry found manifest", prompt_name),
                "None".into(),
            ));
        }
    };

    match uri {
        Uri::Local(path) => fs_utils::copy_file(&path, &prompt_path),
        Uri::Remote(url) => fs_utils::download_file(&url, &prompt_path).await,
    }
}

pub async fn fetch_quest(quest_name: &str) -> Result<()> {
    let manifest_path = fs_utils::ensure_path_from_home(&[OWL_DIR], Some(MANIFEST))?;
    let quest_dir = fs_utils::ensure_path_from_home(&[OWL_DIR], Some(quest_name))?;

    if !manifest_path.exists() {
        return Err(OwlError::FileError(
            "The manifest does not exist".into(),
            "".into(),
        ));
    }

    let manifest_doc = toml_utils::read_toml(&manifest_path)?;
    let quest_entry = manifest_doc["personal_quests"]
        .get(quest_name)
        .or(manifest_doc["quests"].get(quest_name));

    let uri = match quest_entry {
        Some(uri_item) => {
            let uri_str = uri_item.as_str().ok_or(OwlError::TomlError(
                format!("Invalid entry '{}' in manifest", quest_name),
                "None".into(),
            ))?;
            Uri::try_from(uri_str)?
        }
        None => {
            return Err(OwlError::TomlError(
                format!("'{}': no such entry found manifest", quest_name),
                "None".into(),
            ));
        }
    };

    match uri {
        Uri::Local(path) => fs_utils::extract_archive(&path, &quest_dir, false).await,
        Uri::Remote(url) => {
            fs_utils::download_archive(&url, Path::new(TMP_ARCHIVE), &quest_dir).await
        }
    }
}