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:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -53,4 +53,7 @@
|
|||||||
"svelte.ask-to-enable-ts-plugin": false,
|
"svelte.ask-to-enable-ts-plugin": false,
|
||||||
"svelte.plugin.css.diagnostics.enable": false,
|
"svelte.plugin.css.diagnostics.enable": false,
|
||||||
"svelte.plugin.svelte.diagnostics.enable": false,
|
"svelte.plugin.svelte.diagnostics.enable": false,
|
||||||
|
"rust-analyzer.linkedProjects": [
|
||||||
|
"./theseus/Cargo.toml"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
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
|
//! API for interacting with Theseus
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod jre;
|
pub mod jre;
|
||||||
|
pub mod logs;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod pack;
|
pub mod pack;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
use daedalus as d;
|
use daedalus as d;
|
||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
use st::Profile;
|
use st::Profile;
|
||||||
|
use std::fs;
|
||||||
use std::{path::Path, process::Stdio, sync::Arc};
|
use std::{path::Path, process::Stdio, sync::Arc};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use uuid::Uuid;
|
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-added env varaibles for debugging, and add settings env vars
|
||||||
clear_cargo_env_vals(&mut command).envs(env_args);
|
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
|
// Create Minecraft child by inserting it into the state
|
||||||
// This also spawns the process and prepares the subsequent processes
|
// This also spawns the process and prepares the subsequent processes
|
||||||
let mut state_children = state.children.write().await;
|
let mut state_children = state.children.write().await;
|
||||||
@@ -329,6 +344,8 @@ pub async fn launch_minecraft(
|
|||||||
.insert_process(
|
.insert_process(
|
||||||
Uuid::new_v4(),
|
Uuid::new_v4(),
|
||||||
instance_path.to_path_buf(),
|
instance_path.to_path_buf(),
|
||||||
|
stdout_log_path,
|
||||||
|
stderr_log_path,
|
||||||
command,
|
command,
|
||||||
post_exit_hook,
|
post_exit_hook,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use super::Profile;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::ExitStatus;
|
use std::process::ExitStatus;
|
||||||
use std::{collections::HashMap, sync::Arc};
|
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::Child;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::process::{ChildStderr, ChildStdout};
|
use tokio::process::{ChildStderr, ChildStdout};
|
||||||
@@ -40,6 +41,8 @@ impl Children {
|
|||||||
&mut self,
|
&mut self,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
profile_path: PathBuf,
|
profile_path: PathBuf,
|
||||||
|
stdout_log_path: PathBuf,
|
||||||
|
stderr_log_path: PathBuf,
|
||||||
mut mc_command: Command,
|
mut mc_command: Command,
|
||||||
post_command: Option<Command>, // Command to run after minecraft.
|
post_command: Option<Command>, // Command to run after minecraft.
|
||||||
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
||||||
@@ -47,7 +50,7 @@ impl Children {
|
|||||||
let mut child = mc_command.spawn()?;
|
let mut child = mc_command.spawn()?;
|
||||||
|
|
||||||
// Create std watcher threads for stdout and stderr
|
// 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() {
|
if let Some(child_stdout) = child.stdout.take() {
|
||||||
let stdout_clone = stdout.clone();
|
let stdout_clone = stdout.clone();
|
||||||
tokio::spawn(async move {
|
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() {
|
if let Some(child_stderr) = child.stderr.take() {
|
||||||
let stderr_clone = stderr.clone();
|
let stderr_clone = stderr.clone();
|
||||||
tokio::spawn(async move {
|
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
|
// 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
|
// 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 {
|
pub struct SharedOutput {
|
||||||
output: Arc<RwLock<String>>,
|
output: Arc<RwLock<String>>,
|
||||||
|
log_file: Arc<RwLock<File>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedOutput {
|
impl SharedOutput {
|
||||||
fn new() -> Self {
|
async fn build(log_file_path: &Path) -> crate::Result<Self> {
|
||||||
SharedOutput {
|
Ok(SharedOutput {
|
||||||
output: Arc::new(RwLock::new(String::new())),
|
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
|
// 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;
|
let mut output = self.output.write().await;
|
||||||
output.push_str(&line);
|
output.push_str(&line);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let mut log_file = self.log_file.write().await;
|
||||||
|
log_file.write_all(line.as_bytes()).await?;
|
||||||
|
}
|
||||||
line.clear();
|
line.clear();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -116,6 +116,14 @@ impl DirectoryInfo {
|
|||||||
self.config_dir.join("profiles")
|
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
|
/// Get the file containing the global database
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn database_file(&self) -> PathBuf {
|
pub fn database_file(&self) -> PathBuf {
|
||||||
|
|||||||
@@ -358,7 +358,9 @@ pub async fn infer_data_from_files(
|
|||||||
.filter(|x| x.team_id == project.team)
|
.filter(|x| x.team_id == project.team)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>(),
|
.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(
|
incompatible: !version.loaders.contains(
|
||||||
&profile
|
&profile
|
||||||
|
|||||||
61
theseus_gui/src-tauri/src/api/logs.rs
Normal file
61
theseus_gui/src-tauri/src/api/logs.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use crate::api::Result;
|
||||||
|
use theseus::logs::{self, Logs};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A log is a struct containing the datetime string, stdout, and stderr, as follows:
|
||||||
|
|
||||||
|
pub struct Logs {
|
||||||
|
pub datetime_string: String,
|
||||||
|
pub stdout: String,
|
||||||
|
pub stderr: String,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Get all Logs for a profile, sorted by datetime
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn logs_get_logs(profile_uuid: Uuid) -> Result<Vec<Logs>> {
|
||||||
|
Ok(logs::get_logs(profile_uuid).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a Log struct for a profile by profile id and datetime string
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn logs_get_logs_by_datetime(
|
||||||
|
profile_uuid: Uuid,
|
||||||
|
datetime_string: String,
|
||||||
|
) -> Result<Logs> {
|
||||||
|
Ok(logs::get_logs_by_datetime(profile_uuid, datetime_string).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the stdout for a profile by profile id and datetime string
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn logs_get_stdout_by_datetime(
|
||||||
|
profile_uuid: Uuid,
|
||||||
|
datetime_string: String,
|
||||||
|
) -> Result<String> {
|
||||||
|
Ok(logs::get_stdout_by_datetime(profile_uuid, &datetime_string).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the stderr for a profile by profile id and datetime string
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn logs_get_stderr_by_datetime(
|
||||||
|
profile_uuid: Uuid,
|
||||||
|
datetime_string: String,
|
||||||
|
) -> Result<String> {
|
||||||
|
Ok(logs::get_stderr_by_datetime(profile_uuid, &datetime_string).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete all logs for a profile by profile id
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn logs_delete_logs(profile_uuid: Uuid) -> Result<()> {
|
||||||
|
Ok(logs::delete_logs(profile_uuid).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a log for a profile by profile id and datetime string
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn logs_delete_logs_by_datetime(
|
||||||
|
profile_uuid: Uuid,
|
||||||
|
datetime_string: String,
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(logs::delete_logs_by_datetime(profile_uuid, &datetime_string).await?)
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ use thiserror::Error;
|
|||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod jre;
|
pub mod jre;
|
||||||
|
pub mod logs;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod pack;
|
pub mod pack;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
|
|||||||
@@ -101,6 +101,12 @@ fn main() {
|
|||||||
api::metadata::metadata_get_game_versions,
|
api::metadata::metadata_get_game_versions,
|
||||||
api::metadata::metadata_get_fabric_versions,
|
api::metadata::metadata_get_fabric_versions,
|
||||||
api::metadata::metadata_get_forge_versions,
|
api::metadata::metadata_get_forge_versions,
|
||||||
|
api::logs::logs_get_logs,
|
||||||
|
api::logs::logs_get_logs_by_datetime,
|
||||||
|
api::logs::logs_get_stdout_by_datetime,
|
||||||
|
api::logs::logs_get_stderr_by_datetime,
|
||||||
|
api::logs::logs_delete_logs,
|
||||||
|
api::logs::logs_delete_logs_by_datetime,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
47
theseus_gui/src/helpers/logs.js
Normal file
47
theseus_gui/src/helpers/logs.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* All theseus API calls return serialized values (both return values and errors);
|
||||||
|
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||||
|
* and deserialized into a usable JS object.
|
||||||
|
*/
|
||||||
|
import { invoke } from '@tauri-apps/api/tauri'
|
||||||
|
|
||||||
|
/*
|
||||||
|
A log is a struct containing the datetime string, stdout, and stderr, as follows:
|
||||||
|
|
||||||
|
pub struct Logs {
|
||||||
|
pub datetime_string: String,
|
||||||
|
pub stdout: String,
|
||||||
|
pub stderr: String,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Get all logs that exist for a given profile
|
||||||
|
/// This is returned as an array of Log objects, sorted by datetime_string (the folder name, when the log was created)
|
||||||
|
export async function get_logs(profileUuid) {
|
||||||
|
return await invoke('logs_get_logs', { profileUuid })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a profile's log by datetime_string (the folder name, when the log was created)
|
||||||
|
export async function get_logs_by_datetime(profileUuid, datetimeString) {
|
||||||
|
return await invoke('logs_get_logs_by_datetime', { profileUuid, datetimeString })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a profile's stdout only by datetime_string (the folder name, when the log was created)
|
||||||
|
export async function get_stdout_by_datetime(profileUuid, datetimeString) {
|
||||||
|
return await invoke('logs_get_stdout_by_datetime', { profileUuid, datetimeString })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a profile's stderr only by datetime_string (the folder name, when the log was created)
|
||||||
|
export async function get_stderr_by_datetime(profileUuid, datetimeString) {
|
||||||
|
return await invoke('logs_get_stderr_by_datetime', { profileUuid, datetimeString })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete a profile's log by datetime_string (the folder name, when the log was created)
|
||||||
|
export async function delete_logs_by_datetime(profileUuid, datetimeString) {
|
||||||
|
return await invoke('logs_delete_logs_by_datetime', { profileUuid, datetimeString })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete all logs for a given profile
|
||||||
|
export async function delete_logs(profileUuid) {
|
||||||
|
return await invoke('logs_delete_logs', { profileUuid })
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user