mcserver 0.3.5

Minecraft Multiplexer
Documentation
use std::path::Path;

use tokio::fs::{self, ReadDir};

use miette::{Context, IntoDiagnostic};

use mcserver::error::{Error, display_path};

const METADATA_DIRECTORY: &str = ".mcserver";

#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ServerGroup {
    pub name: String,
    pub children: Vec<ServerEntry>,
    pub expanded: bool,
}

#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ServerObject {
    pub name: String,
    pub online: bool,
}

#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum ServerEntry {
    Group(ServerGroup),
    Object(ServerObject),
}

impl ServerEntry {
    fn group(name: String, children: Vec<ServerEntry>) -> Self {
        Self::Group(ServerGroup {
            name,
            children,
            expanded: false,
        })
    }

    fn object(name: String, online: bool) -> Self {
        Self::Object(ServerObject { name, online })
    }
}

pub async fn make_read_dir(dir: &Path) -> miette::Result<ReadDir> {
    fs::read_dir(dir)
        .await
        .into_diagnostic()
        .wrap_err_with(|| format!("Failed to read directory `{}`", display_path(dir)))
}

#[async_recursion::async_recursion]
pub async fn read(dir: &Path) -> miette::Result<Vec<ServerEntry>> {
    let mut children: Vec<ServerEntry> = vec![];
    let mut read_dir = make_read_dir(dir).await?;

    while let Some(entry) = read_dir
        .next_entry()
        .await
        .into_diagnostic()
        .wrap_err_with(|| {
            format!(
                "Failed to read entry from directory `{}`",
                display_path(dir)
            )
        })?
    {
        let entry_path = entry.path();

        let relative_path = entry_path
            .strip_prefix(dir)
            .into_diagnostic()
            .wrap_err_with(|| {
                format!(
                    "Failed to strip `{}` from `{}`",
                    display_path(dir),
                    display_path(&entry_path)
                )
            })?
            .to_path_buf()
            .into_string()
            .map_err(Error::NonUtf8Path)
            .into_diagnostic()?;

        if fs::try_exists(entry_path.join(METADATA_DIRECTORY))
            .await
            .into_diagnostic()
            .wrap_err("Failed to check if directory exists")?
        {
            children.push(ServerEntry::object(relative_path, false));
        } else {
            children.push(ServerEntry::group(relative_path, read(&entry_path).await?));
        }
    }

    children.sort_unstable();

    Ok(children)
}

// TODO: replace these functions with tests

#[allow(dead_code)]
fn print_entries(entries: &[ServerEntry], depth: usize) {
    let increased_depth = depth + 1;

    let base_indentation = " ".repeat(depth.saturating_sub(1) * 2);

    for entry in entries {
        match entry {
            ServerEntry::Group(server_group) => {
                println!("{base_indentation}v {}", server_group.name);
                print_entries(&server_group.children, increased_depth)
            }
            ServerEntry::Object(server_object) => {
                println!("{base_indentation}->{}", server_object.name);
            }
        }
    }
}

#[allow(dead_code)]
#[tokio::main]
async fn main() -> miette::Result<()> {
    let dirs = read(Path::new("/home/finnian/old-servers/")).await?;

    println!("Servers:");
    print_entries(&dirs, 1);

    Ok(())
}