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.plugin.css.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
|
||||
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
|
||||
|
||||
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 jre;
|
||||
|
||||
pub mod logs;
|
||||
pub mod metadata;
|
||||
pub mod pack;
|
||||
pub mod process;
|
||||
|
||||
@@ -101,6 +101,12 @@ fn main() {
|
||||
api::metadata::metadata_get_game_versions,
|
||||
api::metadata::metadata_get_fabric_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!())
|
||||
.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