You've already forked AstralRinth
forked from didirus/AstralRinth
Switch from stdout log to latest log MOD-595 (#964)
* Switch from stdout log to latest log * remove std capture * Remove unused functions
This commit is contained in:
@@ -241,14 +241,6 @@ pub async fn get_latest_log_cursor(
|
|||||||
get_generic_live_log_cursor(profile_path, "latest.log", cursor).await
|
get_generic_live_log_cursor(profile_path, "latest.log", cursor).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
|
||||||
pub async fn get_std_log_cursor(
|
|
||||||
profile_path: ProfilePathId,
|
|
||||||
cursor: u64, // 0 to start at beginning of file
|
|
||||||
) -> crate::Result<LatestLogCursor> {
|
|
||||||
get_generic_live_log_cursor(profile_path, "latest_stdout.log", cursor).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn get_generic_live_log_cursor(
|
pub async fn get_generic_live_log_cursor(
|
||||||
profile_path: ProfilePathId,
|
profile_path: ProfilePathId,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use daedalus as d;
|
|||||||
use daedalus::minecraft::{RuleAction, VersionInfo};
|
use daedalus::minecraft::{RuleAction, VersionInfo};
|
||||||
use st::Profile;
|
use st::Profile;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::{process::Stdio, sync::Arc};
|
use std::sync::Arc;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -511,9 +511,7 @@ pub async fn launch_minecraft(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
.current_dir(instance_path.clone())
|
.current_dir(instance_path.clone());
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.stderr(Stdio::piped());
|
|
||||||
|
|
||||||
// CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground
|
// CARGO-set DYLD_LIBRARY_PATH breaks Minecraft on macOS during testing on playground
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
use super::DirectoryInfo;
|
|
||||||
use super::{Profile, ProfilePathId};
|
use super::{Profile, ProfilePathId};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::path::Path;
|
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
use sysinfo::PidExt;
|
use sysinfo::PidExt;
|
||||||
use tokio::fs::File;
|
|
||||||
use tokio::io::AsyncBufReadExt;
|
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
use tokio::io::BufReader;
|
|
||||||
use tokio::process::Child;
|
use tokio::process::Child;
|
||||||
use tokio::process::ChildStderr;
|
|
||||||
use tokio::process::ChildStdout;
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
use crate::event::emit::emit_process;
|
use crate::event::emit::emit_process;
|
||||||
use crate::event::ProcessPayloadType;
|
use crate::event::ProcessPayloadType;
|
||||||
@@ -201,7 +192,6 @@ impl ChildType {
|
|||||||
pub struct MinecraftChild {
|
pub struct MinecraftChild {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub profile_relative_path: ProfilePathId,
|
pub profile_relative_path: ProfilePathId,
|
||||||
pub output: Option<SharedOutput>,
|
|
||||||
pub manager: Option<JoinHandle<crate::Result<i32>>>, // None when future has completed and been handled
|
pub manager: Option<JoinHandle<crate::Result<i32>>>, // None when future has completed and been handled
|
||||||
pub current_child: Arc<RwLock<ChildType>>,
|
pub current_child: Arc<RwLock<ChildType>>,
|
||||||
pub last_updated_playtime: DateTime<Utc>, // The last time we updated the playtime for the associated profile
|
pub last_updated_playtime: DateTime<Utc>, // The last time we updated the playtime for the associated profile
|
||||||
@@ -281,44 +271,9 @@ impl Children {
|
|||||||
censor_strings: HashMap<String, String>,
|
censor_strings: HashMap<String, String>,
|
||||||
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
) -> crate::Result<Arc<RwLock<MinecraftChild>>> {
|
||||||
// Takes the first element of the commands vector and spawns it
|
// Takes the first element of the commands vector and spawns it
|
||||||
let mut child = mc_command.spawn().map_err(IOError::from)?;
|
let mc_proc = mc_command.spawn().map_err(IOError::from)?;
|
||||||
|
|
||||||
// Create std watcher threads for stdout and stderr
|
let child = ChildType::TokioChild(mc_proc);
|
||||||
let log_path = DirectoryInfo::profile_logs_dir(&profile_relative_path)
|
|
||||||
.await?
|
|
||||||
.join("latest_stdout.log");
|
|
||||||
let shared_output =
|
|
||||||
SharedOutput::build(&log_path, censor_strings).await?;
|
|
||||||
if let Some(child_stdout) = child.stdout.take() {
|
|
||||||
let stdout_clone = shared_output.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
if let Err(e) = stdout_clone.read_stdout(child_stdout).await {
|
|
||||||
error!("Stdout process died with error: {}", e);
|
|
||||||
let _ = stdout_clone
|
|
||||||
.push_line(format!(
|
|
||||||
"Stdout process died with error: {}",
|
|
||||||
e
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if let Some(child_stderr) = child.stderr.take() {
|
|
||||||
let stderr_clone = shared_output.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
if let Err(e) = stderr_clone.read_stderr(child_stderr).await {
|
|
||||||
error!("Stderr process died with error: {}", e);
|
|
||||||
let _ = stderr_clone
|
|
||||||
.push_line(format!(
|
|
||||||
"Stderr process died with error: {}",
|
|
||||||
e
|
|
||||||
))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let child = ChildType::TokioChild(child);
|
|
||||||
|
|
||||||
// Slots child into manager
|
// Slots child into manager
|
||||||
let pid = child.id().ok_or_else(|| {
|
let pid = child.id().ok_or_else(|| {
|
||||||
@@ -358,7 +313,6 @@ impl Children {
|
|||||||
let mchild = MinecraftChild {
|
let mchild = MinecraftChild {
|
||||||
uuid,
|
uuid,
|
||||||
profile_relative_path,
|
profile_relative_path,
|
||||||
output: Some(shared_output),
|
|
||||||
current_child,
|
current_child,
|
||||||
manager,
|
manager,
|
||||||
last_updated_playtime,
|
last_updated_playtime,
|
||||||
@@ -449,7 +403,6 @@ impl Children {
|
|||||||
let mchild = MinecraftChild {
|
let mchild = MinecraftChild {
|
||||||
uuid: cached_process.uuid,
|
uuid: cached_process.uuid,
|
||||||
profile_relative_path: cached_process.profile_relative_path,
|
profile_relative_path: cached_process.profile_relative_path,
|
||||||
output: None, // No output for cached/rescued processes
|
|
||||||
current_child,
|
current_child,
|
||||||
manager,
|
manager,
|
||||||
last_updated_playtime,
|
last_updated_playtime,
|
||||||
@@ -758,117 +711,3 @@ impl Default for Children {
|
|||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(Debug, Clone)]
|
|
||||||
pub struct SharedOutput {
|
|
||||||
log_file: Arc<RwLock<File>>,
|
|
||||||
censor_strings: HashMap<String, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SharedOutput {
|
|
||||||
#[tracing::instrument(skip(censor_strings))]
|
|
||||||
async fn build(
|
|
||||||
log_file_path: &Path,
|
|
||||||
censor_strings: HashMap<String, String>,
|
|
||||||
) -> crate::Result<Self> {
|
|
||||||
// create log_file_path parent if it doesn't exist
|
|
||||||
let parent_folder = log_file_path.parent().ok_or_else(|| {
|
|
||||||
crate::ErrorKind::LauncherError(format!(
|
|
||||||
"Could not get parent folder of {:?}",
|
|
||||||
log_file_path
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
tokio::fs::create_dir_all(parent_folder)
|
|
||||||
.await
|
|
||||||
.map_err(|e| IOError::with_path(e, parent_folder))?;
|
|
||||||
|
|
||||||
Ok(SharedOutput {
|
|
||||||
log_file: Arc::new(RwLock::new(
|
|
||||||
File::create(log_file_path)
|
|
||||||
.await
|
|
||||||
.map_err(|e| IOError::with_path(e, log_file_path))?,
|
|
||||||
)),
|
|
||||||
censor_strings,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read_stdout(
|
|
||||||
&self,
|
|
||||||
child_stdout: ChildStdout,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let mut buf_reader = BufReader::new(child_stdout);
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
|
|
||||||
while buf_reader
|
|
||||||
.read_until(b'\n', &mut buf)
|
|
||||||
.await
|
|
||||||
.map_err(IOError::from)?
|
|
||||||
> 0
|
|
||||||
{
|
|
||||||
let line = String::from_utf8_lossy(&buf).into_owned();
|
|
||||||
let val_line = self.censor_log(line.clone());
|
|
||||||
{
|
|
||||||
let mut log_file = self.log_file.write().await;
|
|
||||||
log_file
|
|
||||||
.write_all(val_line.as_bytes())
|
|
||||||
.await
|
|
||||||
.map_err(IOError::from)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.clear();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read_stderr(
|
|
||||||
&self,
|
|
||||||
child_stderr: ChildStderr,
|
|
||||||
) -> crate::Result<()> {
|
|
||||||
let mut buf_reader = BufReader::new(child_stderr);
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
|
|
||||||
// TODO: these can be asbtracted into noe function
|
|
||||||
while buf_reader
|
|
||||||
.read_until(b'\n', &mut buf)
|
|
||||||
.await
|
|
||||||
.map_err(IOError::from)?
|
|
||||||
> 0
|
|
||||||
{
|
|
||||||
let line = String::from_utf8_lossy(&buf).into_owned();
|
|
||||||
let val_line = self.censor_log(line.clone());
|
|
||||||
{
|
|
||||||
let mut log_file = self.log_file.write().await;
|
|
||||||
log_file
|
|
||||||
.write_all(val_line.as_bytes())
|
|
||||||
.await
|
|
||||||
.map_err(IOError::from)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.clear();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn push_line(&self, line: String) -> crate::Result<()> {
|
|
||||||
let val_line = self.censor_log(line.clone());
|
|
||||||
{
|
|
||||||
let mut log_file = self.log_file.write().await;
|
|
||||||
log_file
|
|
||||||
.write_all(val_line.as_bytes())
|
|
||||||
.await
|
|
||||||
.map_err(IOError::from)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn censor_log(&self, mut val: String) -> String {
|
|
||||||
for (find, replace) in &self.censor_strings {
|
|
||||||
val = val.replace(find, replace);
|
|
||||||
}
|
|
||||||
|
|
||||||
val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
|||||||
logs_delete_logs,
|
logs_delete_logs,
|
||||||
logs_delete_logs_by_filename,
|
logs_delete_logs_by_filename,
|
||||||
logs_get_latest_log_cursor,
|
logs_get_latest_log_cursor,
|
||||||
logs_get_std_log_cursor,
|
|
||||||
])
|
])
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -91,12 +90,3 @@ pub async fn logs_get_latest_log_cursor(
|
|||||||
) -> Result<LatestLogCursor> {
|
) -> Result<LatestLogCursor> {
|
||||||
Ok(logs::get_latest_log_cursor(profile_path, cursor).await?)
|
Ok(logs::get_latest_log_cursor(profile_path, cursor).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get live stdout log from a cursor
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn logs_get_std_log_cursor(
|
|
||||||
profile_path: ProfilePathId,
|
|
||||||
cursor: u64, // 0 to start at beginning of file
|
|
||||||
) -> Result<LatestLogCursor> {
|
|
||||||
Ok(logs::get_std_log_cursor(profile_path, cursor).await?)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -54,7 +54,3 @@ export async function delete_logs(profilePath) {
|
|||||||
export async function get_latest_log_cursor(profilePath, cursor) {
|
export async function get_latest_log_cursor(profilePath, cursor) {
|
||||||
return await invoke('plugin:logs|logs_get_latest_log_cursor', { profilePath, cursor })
|
return await invoke('plugin:logs|logs_get_latest_log_cursor', { profilePath, cursor })
|
||||||
}
|
}
|
||||||
// For std log (from modrinth app written latest_stdout.log, contains stdout and stderr)
|
|
||||||
export async function get_std_log_cursor(profilePath, cursor) {
|
|
||||||
return await invoke('plugin:logs|logs_get_std_log_cursor', { profilePath, cursor })
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ import {
|
|||||||
delete_logs_by_filename,
|
delete_logs_by_filename,
|
||||||
get_logs,
|
get_logs,
|
||||||
get_output_by_filename,
|
get_output_by_filename,
|
||||||
get_std_log_cursor,
|
get_latest_log_cursor,
|
||||||
} from '@/helpers/logs.js'
|
} from '@/helpers/logs.js'
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -139,7 +139,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const currentLiveLog = ref(null)
|
const currentLiveLog = ref(null)
|
||||||
const currentLiveLogCursor = ref(0)
|
const currentLiveLogCursor = ref(0)
|
||||||
const emptyText = ['No live game detected.', 'Start your game to proceed']
|
const emptyText = ['No live game detected.', 'Start your game to proceed.']
|
||||||
|
|
||||||
const logs = ref([])
|
const logs = ref([])
|
||||||
await setLogs()
|
await setLogs()
|
||||||
@@ -223,7 +223,7 @@ async function getLiveStdLog() {
|
|||||||
if (uuids.length === 0) {
|
if (uuids.length === 0) {
|
||||||
returnValue = emptyText.join('\n')
|
returnValue = emptyText.join('\n')
|
||||||
} else {
|
} else {
|
||||||
const logCursor = await get_std_log_cursor(
|
const logCursor = await get_latest_log_cursor(
|
||||||
props.instance.path,
|
props.instance.path,
|
||||||
currentLiveLogCursor.value
|
currentLiveLogCursor.value
|
||||||
).catch(handleError)
|
).catch(handleError)
|
||||||
@@ -243,31 +243,15 @@ async function getLogs() {
|
|||||||
return (await get_logs(props.instance.path, true).catch(handleError))
|
return (await get_logs(props.instance.path, true).catch(handleError))
|
||||||
.reverse()
|
.reverse()
|
||||||
.filter(
|
.filter(
|
||||||
|
// filter out latest_stdout.log or anything without .log in it
|
||||||
(log) =>
|
(log) =>
|
||||||
log.filename !== 'latest_stdout.log' &&
|
log.filename !== 'latest_stdout.log' &&
|
||||||
log.filename !== 'latest_stdout' &&
|
log.filename !== 'latest_stdout' &&
|
||||||
log.stdout !== ''
|
log.stdout !== '' &&
|
||||||
|
log.filename.includes('.log')
|
||||||
)
|
)
|
||||||
.map((log) => {
|
.map((log) => {
|
||||||
if (log.filename == 'latest.log') {
|
log.name = log.filename || 'Unknown'
|
||||||
log.name = 'Latest Log'
|
|
||||||
} else {
|
|
||||||
let filename = log.filename.split('.')[0]
|
|
||||||
let day = dayjs(filename.slice(0, 10))
|
|
||||||
if (day.isValid()) {
|
|
||||||
if (day.isToday()) {
|
|
||||||
log.name = 'Today'
|
|
||||||
} else if (day.isYesterday()) {
|
|
||||||
log.name = 'Yesterday'
|
|
||||||
} else {
|
|
||||||
log.name = day.format('MMMM D, YYYY')
|
|
||||||
}
|
|
||||||
// Displays as "Today-1", "Today-2", etc, matching minecraft log naming but with the date
|
|
||||||
log.name = log.name + filename.slice(10)
|
|
||||||
} else {
|
|
||||||
log.name = filename
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.stdout = 'Loading...'
|
log.stdout = 'Loading...'
|
||||||
return log
|
return log
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user