cobble_core/minecraft/log_files/
mod.rs

1mod log_file;
2
3use crate::error::{CobbleError, CobbleResult};
4use futures::TryStreamExt;
5pub use log_file::*;
6use std::path::{Path, PathBuf};
7use std::time::SystemTime;
8use tokio::fs::{metadata, read_dir};
9use tokio_stream::wrappers::ReadDirStream;
10
11/// Loads all log files from the minecraft folder.
12#[cfg_attr(doc_cfg, doc(cfg(feature = "log-files")))]
13#[instrument(
14    name = "load_log_files",
15    level = "debug",
16    skip_all,
17    fields(minecraft_path)
18)]
19pub async fn load_log_files(minecraft_path: impl AsRef<Path> + Send) -> CobbleResult<Vec<LogFile>> {
20    let mut logs_path = PathBuf::from(minecraft_path.as_ref());
21    logs_path.push("logs");
22
23    if !logs_path.is_dir() {
24        trace!("Logs directory is empty");
25        return Ok(vec![]);
26    }
27
28    trace!("Loading log files...");
29    let file_stream = ReadDirStream::new(read_dir(logs_path).await?);
30    let log_files = file_stream
31        .map_err(CobbleError::from)
32        .try_filter_map(|e| parse_log_file(e.path()))
33        .try_collect()
34        .await?;
35
36    Ok(log_files)
37}
38
39#[cfg_attr(doc_cfg, doc(cfg(feature = "log-files")))]
40#[instrument(name = "parse_log_file", level = "trace", skip_all, fields(path,))]
41async fn parse_log_file(path: impl AsRef<Path>) -> CobbleResult<Option<LogFile>> {
42    // Check if file
43    if !path.as_ref().is_file() {
44        trace!("Entry is not a file.");
45        return Ok(None);
46    }
47
48    // Check mime
49    let mime = match mime_guess::from_path(&path).first() {
50        Some(mime) => mime,
51        None => {
52            trace!("Could not get MIME type for file.");
53            return Ok(None);
54        }
55    };
56    let _type = if mime == mime_guess::mime::TEXT_PLAIN {
57        LogFileType::Plain
58    } else if mime == "application/gzip" {
59        LogFileType::GzipCompressed
60    } else {
61        trace!("Entry is neither plain text nor gzip compressed file");
62        return Ok(None);
63    };
64
65    // Parse
66    let name = match path.as_ref().file_name() {
67        Some(name) => name.to_string_lossy().to_string(),
68        None => return Ok(None),
69    };
70
71    let modified = metadata(path.as_ref())
72        .await
73        .and_then(|m| m.modified())
74        .ok()
75        .and_then(|st| st.duration_since(SystemTime::UNIX_EPOCH).ok())
76        .and_then(|d| time::OffsetDateTime::from_unix_timestamp(d.as_secs() as i64).ok());
77
78    Ok(Some(LogFile {
79        name,
80        path: PathBuf::from(path.as_ref()),
81        _type,
82        modified,
83    }))
84}