* not compiling; just to commit

* working logs commit

* prettier; clippy

* delete logs functions

* Reverted change

* mislabeled doc tag
This commit is contained in:
Wyatt Verchere
2023-05-03 09:02:34 -07:00
committed by GitHub
parent 713a915161
commit edd9f28081
11 changed files with 275 additions and 9 deletions

View File

@@ -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
View 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(())
}

View File

@@ -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;

View File

@@ -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,
)

View File

@@ -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(())

View File

@@ -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 {

View File

@@ -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

View 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?)
}

View File

@@ -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;

View File

@@ -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");

View 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 })
}