cobble-core 1.2.0

Library for managing, installing and launching Minecraft instances and more.
Documentation
mod log_file;

use crate::error::{CobbleError, CobbleResult};
use futures::TryStreamExt;
pub use log_file::*;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use tokio::fs::{metadata, read_dir};
use tokio_stream::wrappers::ReadDirStream;

/// Loads all log files from the minecraft folder.
#[cfg_attr(doc_cfg, doc(cfg(feature = "log-files")))]
#[instrument(
    name = "load_log_files",
    level = "debug",
    skip_all,
    fields(minecraft_path)
)]
pub async fn load_log_files(minecraft_path: impl AsRef<Path> + Send) -> CobbleResult<Vec<LogFile>> {
    let mut logs_path = PathBuf::from(minecraft_path.as_ref());
    logs_path.push("logs");

    if !logs_path.is_dir() {
        trace!("Logs directory is empty");
        return Ok(vec![]);
    }

    trace!("Loading log files...");
    let file_stream = ReadDirStream::new(read_dir(logs_path).await?);
    let log_files = file_stream
        .map_err(CobbleError::from)
        .try_filter_map(|e| parse_log_file(e.path()))
        .try_collect()
        .await?;

    Ok(log_files)
}

#[cfg_attr(doc_cfg, doc(cfg(feature = "log-files")))]
#[instrument(name = "parse_log_file", level = "trace", skip_all, fields(path,))]
async fn parse_log_file(path: impl AsRef<Path>) -> CobbleResult<Option<LogFile>> {
    // Check if file
    if !path.as_ref().is_file() {
        trace!("Entry is not a file.");
        return Ok(None);
    }

    // Check mime
    let mime = match mime_guess::from_path(&path).first() {
        Some(mime) => mime,
        None => {
            trace!("Could not get MIME type for file.");
            return Ok(None);
        }
    };
    let _type = if mime == mime_guess::mime::TEXT_PLAIN {
        LogFileType::Plain
    } else if mime == "application/gzip" {
        LogFileType::GzipCompressed
    } else {
        trace!("Entry is neither plain text nor gzip compressed file");
        return Ok(None);
    };

    // Parse
    let name = match path.as_ref().file_name() {
        Some(name) => name.to_string_lossy().to_string(),
        None => return Ok(None),
    };

    let modified = metadata(path.as_ref())
        .await
        .and_then(|m| m.modified())
        .ok()
        .and_then(|st| st.duration_since(SystemTime::UNIX_EPOCH).ok())
        .and_then(|d| time::OffsetDateTime::from_unix_timestamp(d.as_secs() as i64).ok());

    Ok(Some(LogFile {
        name,
        path: PathBuf::from(path.as_ref()),
        _type,
        modified,
    }))
}