Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions src-tauri/src/commands/file_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,98 @@ pub fn get_data_dir() -> Result<String, String> {
Ok(data_dir.to_string_lossy().to_string())
}

/// 统计 debug 目录下 .log 文件总大小,可排除当前正在写入的日志文件
#[tauri::command]
pub fn get_log_dir_size(exclude_file_name: Option<String>) -> Result<u64, String> {
let debug_dir = get_app_data_dir()?.join("debug");

if !debug_dir.exists() {
return Ok(0);
}

let mut total = 0_u64;
let entries =
std::fs::read_dir(&debug_dir).map_err(|e| format!("读取日志目录失败 [{}]: {}", debug_dir.display(), e))?;

for entry in entries {
let entry = match entry {
Ok(entry) => entry,
Err(_) => continue,
};
let path = entry.path();
if !path.is_file() {
continue;
}

let Some(name) = path.file_name().and_then(|name| name.to_str()) else {
continue;
};

if !name.ends_with(".log") {
continue;
}

if exclude_file_name.as_deref() == Some(name) {
continue;
}

match path.metadata() {
Ok(metadata) => {
total = total.saturating_add(metadata.len());
}
Err(e) => {
log::debug!("Failed to read log file size [{}]: {}", path.display(), e);
}
}
}

Ok(total)
}

/// 删除 debug 目录中的 .log 文件,可选择排除一个当前正在使用的日志文件
#[tauri::command]
pub fn clear_log_files(exclude_file_name: Option<String>) -> Result<u64, String> {
let debug_dir = get_app_data_dir()?.join("debug");

if !debug_dir.exists() {
return Ok(0);
}

let mut deleted = 0_u64;
let entries =
std::fs::read_dir(&debug_dir).map_err(|e| format!("读取日志目录失败 [{}]: {}", debug_dir.display(), e))?;

for entry in entries {
let entry = match entry {
Ok(entry) => entry,
Err(_) => continue,
};
let path = entry.path();
if !path.is_file() {
continue;
}

let Some(name) = path.file_name().and_then(|name| name.to_str()) else {
continue;
};

if !name.ends_with(".log") {
continue;
}

if exclude_file_name.as_deref() == Some(name) {
continue;
}

match std::fs::remove_file(&path) {
Ok(()) => deleted = deleted.saturating_add(1),
Err(e) => log::debug!("Failed to delete log file [{}]: {}", path.display(), e),
}
}

Ok(deleted)
}

/// 获取当前工作目录
#[tauri::command]
pub fn get_cwd() -> Result<String, String> {
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ pub fn run() {
commands::file_ops::local_file_exists,
commands::file_ops::get_exe_dir,
commands::file_ops::get_data_dir,
commands::file_ops::get_log_dir_size,
commands::file_ops::clear_log_files,
commands::file_ops::get_cwd,
commands::file_ops::check_exe_path,
commands::file_ops::set_executable,
Expand Down
48 changes: 35 additions & 13 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { getInterfaceLangKey } from '@/i18n';
import { applyTheme, resolveThemeMode, registerCustomAccent, clearCustomAccents } from '@/themes';
import { Toaster } from 'sonner';
import { loadWebUIAppearance, loadWebUILayout } from '@/services/appearanceStorage';
import { loadPersistedRuntimeLogs, mergeRuntimeLogs, persistRuntimeLogs } from '@/utils/runtimeLogPersistence';
import {
isTauri,
isValidWindowSize,
Expand Down Expand Up @@ -655,22 +656,43 @@ function App() {
// 从后端恢复运行日志(跨页面刷新持久化)
try {
const backendLogs = await getAllLogsFromBackend();
if (backendLogs && Object.keys(backendLogs).length > 0) {
const store = useAppStore.getState();
const restoredLogs: Record<string, import('@/stores/types').LogEntry[]> = {};
for (const [instanceId, entries] of Object.entries(backendLogs)) {
restoredLogs[instanceId] = entries.map((e) => ({
id: e.id,
timestamp: new Date(e.timestamp),
type: e.type as import('@/stores/types').LogType,
message: e.message,
html: e.html,
}));
const store = useAppStore.getState();
const clearLogFilesOnLaunch = async () => {
if (!isTauri()) return;
try {
const deleted = await invoke<number>('clear_log_files', {});
log.info('Auto-cleared log files on launch:', deleted);
} catch {
// ignore cleanup errors
}
};
const restoredBackendLogs: Record<string, import('@/stores/types').LogEntry[]> = {};
for (const [instanceId, entries] of Object.entries(backendLogs || {})) {
restoredBackendLogs[instanceId] = entries.map((e) => ({
id: e.id,
timestamp: new Date(e.timestamp),
type: e.type as import('@/stores/types').LogType,
message: e.message,
html: e.html,
}));
}
if (store.autoClearLogsOnLaunch) {
await clearLogFilesOnLaunch();
}

const restoredPersistentLogs = loadPersistedRuntimeLogs(store.maxLogsPerInstance);
const mergedLogs = mergeRuntimeLogs(
store.maxLogsPerInstance,
store.instanceLogs,
restoredBackendLogs,
restoredPersistentLogs,
);
if (Object.keys(mergedLogs).length > 0) {
useAppStore.setState({
instanceLogs: { ...store.instanceLogs, ...restoredLogs },
instanceLogs: mergedLogs,
});
log.info('已恢复运行日志:', Object.keys(restoredLogs).length, '个实例');
persistRuntimeLogs(mergedLogs, store.maxLogsPerInstance);
log.info('Restored runtime logs', Object.keys(mergedLogs).length, 'instances');
}
} catch (err) {
log.warn('恢复运行日志失败:', err);
Expand Down
Loading
Loading