You've already forked AstralRinth
forked from didirus/AstralRinth
Logs api (#103)
* not compiling; just to commit * working logs commit * prettier; clippy * delete logs functions * Reverted change * mislabeled doc tag
This commit is contained in:
112
theseus/src/api/logs.rs
Normal file
112
theseus/src/api/logs.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use crate::State;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs::read_to_string;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Logs {
|
||||
pub datetime_string: String,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
impl Logs {
|
||||
async fn build(
|
||||
profile_uuid: uuid::Uuid,
|
||||
datetime_string: String,
|
||||
) -> crate::Result<Self> {
|
||||
Ok(Self {
|
||||
stdout: get_stdout_by_datetime(profile_uuid, &datetime_string)
|
||||
.await?,
|
||||
stderr: get_stderr_by_datetime(profile_uuid, &datetime_string)
|
||||
.await?,
|
||||
datetime_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_logs(profile_uuid: uuid::Uuid) -> crate::Result<Vec<Logs>> {
|
||||
let state = State::get().await?;
|
||||
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
|
||||
let mut logs = Vec::new();
|
||||
for entry in std::fs::read_dir(logs_folder)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if let Some(datetime_string) = path.file_name() {
|
||||
logs.push(
|
||||
Logs::build(
|
||||
profile_uuid,
|
||||
datetime_string.to_string_lossy().to_string(),
|
||||
)
|
||||
.await,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut logs = logs.into_iter().collect::<crate::Result<Vec<Logs>>>()?;
|
||||
logs.sort_by_key(|x| x.datetime_string.clone());
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_logs_by_datetime(
|
||||
profile_uuid: uuid::Uuid,
|
||||
datetime_string: String,
|
||||
) -> crate::Result<Logs> {
|
||||
Ok(Logs {
|
||||
stdout: get_stdout_by_datetime(profile_uuid, &datetime_string).await?,
|
||||
stderr: get_stderr_by_datetime(profile_uuid, &datetime_string).await?,
|
||||
datetime_string,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_stdout_by_datetime(
|
||||
profile_uuid: uuid::Uuid,
|
||||
datetime_string: &str,
|
||||
) -> crate::Result<String> {
|
||||
let state = State::get().await?;
|
||||
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
|
||||
Ok(
|
||||
read_to_string(logs_folder.join(datetime_string).join("stdout.log"))
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_stderr_by_datetime(
|
||||
profile_uuid: uuid::Uuid,
|
||||
datetime_string: &str,
|
||||
) -> crate::Result<String> {
|
||||
let state = State::get().await?;
|
||||
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
|
||||
Ok(
|
||||
read_to_string(logs_folder.join(datetime_string).join("stderr.log"))
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn delete_logs(profile_uuid: uuid::Uuid) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
|
||||
for entry in std::fs::read_dir(logs_folder)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
std::fs::remove_dir_all(path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn delete_logs_by_datetime(
|
||||
profile_uuid: uuid::Uuid,
|
||||
datetime_string: &str,
|
||||
) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
let logs_folder = state.directories.profile_logs_dir(profile_uuid);
|
||||
std::fs::remove_dir_all(logs_folder.join(datetime_string))?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
//! API for interacting with Theseus
|
||||
pub mod auth;
|
||||
pub mod jre;
|
||||
pub mod logs;
|
||||
pub mod metadata;
|
||||
pub mod pack;
|
||||
pub mod process;
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::{
|
||||
use daedalus as d;
|
||||
use dunce::canonicalize;
|
||||
use st::Profile;
|
||||
use std::fs;
|
||||
use std::{path::Path, process::Stdio, sync::Arc};
|
||||
use tokio::process::Command;
|
||||
use uuid::Uuid;
|
||||
@@ -322,6 +323,20 @@ pub async fn launch_minecraft(
|
||||
// Clear cargo-added env varaibles for debugging, and add settings env vars
|
||||
clear_cargo_env_vals(&mut command).envs(env_args);
|
||||
|
||||
// Get Modrinth logs directories
|
||||
let datetime_string =
|
||||
chrono::Local::now().format("%Y%m%y_%H%M%S").to_string();
|
||||
let logs_dir = {
|
||||
let st = State::get().await?;
|
||||
st.directories
|
||||
.profile_logs_dir(profile.uuid)
|
||||
.join(&datetime_string)
|
||||
};
|
||||
fs::create_dir_all(&logs_dir)?;
|
||||
|
||||
let stdout_log_path = logs_dir.join("stdout.log");
|
||||
let stderr_log_path = logs_dir.join("stderr.log");
|
||||
|
||||
// Create Minecraft child by inserting it into the state
|
||||
// This also spawns the process and prepares the subsequent processes
|
||||
let mut state_children = state.children.write().await;
|
||||
@@ -329,6 +344,8 @@ pub async fn launch_minecraft(
|
||||
.insert_process(
|
||||
Uuid::new_v4(),
|
||||
instance_path.to_path_buf(),
|
||||
stdout_log_path,
|
||||
stderr_log_path,
|
||||
command,
|
||||
post_exit_hook,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,8 @@ use super::Profile;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitStatus;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::process::Child;
|
||||
use tokio::process::Command;
|
||||
use tokio::process::{ChildStderr, ChildStdout};
|
||||
@@ -40,6 +41,8 @@ impl Children {
|
||||
&mut self,
|
||||
uuid: Uuid,
|
||||
profile_path: PathBuf,
|
||||
stdout_log_path: PathBuf,
|
||||
stderr_log_path: PathBuf,
|
||||
mut mc_command: Command,
|
||||
post_command: Option<Command>, // Command to run after minecraft.
|
||||
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
||||
@@ -47,7 +50,7 @@ impl Children {
|
||||
let mut child = mc_command.spawn()?;
|
||||
|
||||
// Create std watcher threads for stdout and stderr
|
||||
let stdout = SharedOutput::new();
|
||||
let stdout = SharedOutput::build(&stdout_log_path).await?;
|
||||
if let Some(child_stdout) = child.stdout.take() {
|
||||
let stdout_clone = stdout.clone();
|
||||
tokio::spawn(async move {
|
||||
@@ -56,7 +59,7 @@ impl Children {
|
||||
}
|
||||
});
|
||||
}
|
||||
let stderr = SharedOutput::new();
|
||||
let stderr = SharedOutput::build(&stderr_log_path).await?;
|
||||
if let Some(child_stderr) = child.stderr.take() {
|
||||
let stderr_clone = stderr.clone();
|
||||
tokio::spawn(async move {
|
||||
@@ -270,16 +273,18 @@ impl Default for Children {
|
||||
|
||||
// SharedOutput, a wrapper around a String that can be read from and written to concurrently
|
||||
// Designed to be used with ChildStdout and ChildStderr in a tokio thread to have a simple String storage for the output of a child process
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SharedOutput {
|
||||
output: Arc<RwLock<String>>,
|
||||
log_file: Arc<RwLock<File>>,
|
||||
}
|
||||
|
||||
impl SharedOutput {
|
||||
fn new() -> Self {
|
||||
SharedOutput {
|
||||
async fn build(log_file_path: &Path) -> crate::Result<Self> {
|
||||
Ok(SharedOutput {
|
||||
output: Arc::new(RwLock::new(String::new())),
|
||||
}
|
||||
log_file: Arc::new(RwLock::new(File::create(log_file_path).await?)),
|
||||
})
|
||||
}
|
||||
|
||||
// Main entry function to a created SharedOutput, returns the log as a String
|
||||
@@ -300,6 +305,10 @@ impl SharedOutput {
|
||||
let mut output = self.output.write().await;
|
||||
output.push_str(&line);
|
||||
}
|
||||
{
|
||||
let mut log_file = self.log_file.write().await;
|
||||
log_file.write_all(line.as_bytes()).await?;
|
||||
}
|
||||
line.clear();
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -116,6 +116,14 @@ impl DirectoryInfo {
|
||||
self.config_dir.join("profiles")
|
||||
}
|
||||
|
||||
/// Gets the logs dir for a given profile
|
||||
#[inline]
|
||||
pub fn profile_logs_dir(&self, profile: uuid::Uuid) -> PathBuf {
|
||||
self.profiles_dir()
|
||||
.join(profile.to_string())
|
||||
.join("modrinth_logs")
|
||||
}
|
||||
|
||||
/// Get the file containing the global database
|
||||
#[inline]
|
||||
pub fn database_file(&self) -> PathBuf {
|
||||
|
||||
@@ -358,7 +358,9 @@ pub async fn infer_data_from_files(
|
||||
.filter(|x| x.team_id == project.team)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
update_version: update_versions.get(&hash).map(|val| Box::new(val.clone())),
|
||||
update_version: update_versions
|
||||
.get(&hash)
|
||||
.map(|val| Box::new(val.clone())),
|
||||
|
||||
incompatible: !version.loaders.contains(
|
||||
&profile
|
||||
|
||||
Reference in New Issue
Block a user