diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 142800c..09334ae 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -11,6 +11,15 @@ { "identifier": "opener:allow-open-path", "allow": [ + { + "path": "$HOME/.codeforge/**" + }, + { + "path": "$APPDATA/**" + }, + { + "path": "$LOCALAPPDATA/**" + }, { "path": "**" } @@ -20,9 +29,19 @@ "shell:allow-open", "dialog:default", "fs:allow-read-text-file", + "fs:allow-read-dir", + "fs:allow-read-file", + "fs:allow-write-file", + "fs:allow-write-text-file", + "fs:allow-remove", + "fs:allow-mkdir", + "fs:allow-exists", { "identifier": "fs:scope", "allow": [ + { + "path": "$HOME/.codeforge/**" + }, { "path": "/var/folders/**" }, @@ -32,6 +51,12 @@ { "path": "/private/var/folders/**" }, + { + "path": "$APPDATA/**" + }, + { + "path": "$LOCALAPPDATA/**" + }, { "path": "**" } diff --git a/src-tauri/src/cache.rs b/src-tauri/src/cache.rs new file mode 100644 index 0000000..df9da5b --- /dev/null +++ b/src-tauri/src/cache.rs @@ -0,0 +1,101 @@ +use log::info; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; + +#[derive(Debug, Serialize, Deserialize)] +pub struct CacheInfo { + pub plugins_cache_size: u64, + pub total_cache_size: u64, +} + +// 获取缓存目录 +fn get_cache_dir() -> Result { + let home_dir = dirs::home_dir().ok_or("无法获取用户主目录")?; + Ok(home_dir.join(".codeforge").join("cache")) +} + +// 获取插件缓存目录 +fn get_plugins_cache_dir() -> Result { + let cache_dir = get_cache_dir()?; + Ok(cache_dir.join("plugins")) +} + +// 计算目录大小 +fn calculate_dir_size(path: &PathBuf) -> u64 { + if !path.exists() { + return 0; + } + + let mut total_size = 0u64; + + if let Ok(entries) = fs::read_dir(path) { + for entry in entries.flatten() { + let path = entry.path(); + if path.is_file() { + if let Ok(metadata) = fs::metadata(&path) { + total_size += metadata.len(); + } + } else if path.is_dir() { + total_size += calculate_dir_size(&path); + } + } + } + + total_size +} + +// 获取缓存信息 +#[tauri::command] +pub fn get_cache_info() -> Result { + info!("获取缓存信息"); + + let cache_dir = get_cache_dir()?; + let plugins_cache_dir = get_plugins_cache_dir()?; + + let plugins_cache_size = calculate_dir_size(&plugins_cache_dir); + let total_cache_size = calculate_dir_size(&cache_dir); + + Ok(CacheInfo { + plugins_cache_size, + total_cache_size, + }) +} + +// 清理插件缓存 +#[tauri::command] +pub fn clear_plugins_cache() -> Result<(), String> { + info!("清理插件缓存"); + + let plugins_cache_dir = get_plugins_cache_dir()?; + + if plugins_cache_dir.exists() { + fs::remove_dir_all(&plugins_cache_dir) + .map_err(|e| format!("删除插件缓存目录失败: {}", e))?; + + // 重新创建空目录 + fs::create_dir_all(&plugins_cache_dir) + .map_err(|e| format!("创建插件缓存目录失败: {}", e))?; + } + + info!("插件缓存已清理"); + Ok(()) +} + +// 清理所有缓存 +#[tauri::command] +pub fn clear_all_cache() -> Result<(), String> { + info!("清理所有缓存"); + + let cache_dir = get_cache_dir()?; + + if cache_dir.exists() { + fs::remove_dir_all(&cache_dir).map_err(|e| format!("删除缓存目录失败: {}", e))?; + + // 重新创建空目录 + fs::create_dir_all(&cache_dir).map_err(|e| format!("创建缓存目录失败: {}", e))?; + } + + info!("所有缓存已清理"); + Ok(()) +} diff --git a/src-tauri/src/env_providers/clojure.rs b/src-tauri/src/env_providers/clojure.rs index 176f91d..470e041 100644 --- a/src-tauri/src/env_providers/clojure.rs +++ b/src-tauri/src/env_providers/clojure.rs @@ -51,7 +51,7 @@ impl ClojureEnvironmentProvider { fn get_default_install_dir() -> PathBuf { let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); - home_dir.join(".codeforge").join("clojure") + home_dir.join(".codeforge").join("plugins").join("clojure") } fn read_cache(&self) -> Option> { diff --git a/src-tauri/src/env_providers/metadata.rs b/src-tauri/src/env_providers/metadata.rs index 0663284..3a021cc 100644 --- a/src-tauri/src/env_providers/metadata.rs +++ b/src-tauri/src/env_providers/metadata.rs @@ -66,7 +66,7 @@ pub async fn fetch_metadata_from_cdn(language: &str) -> Result .and_then(|m| m.base_url.as_ref()) .ok_or("CDN 地址未配置")?; - let metadata_url = format!("{}/{}/metadata.json", base_url, language); + let metadata_url = format!("{}/global/plugins/{}/metadata.json", base_url, language); info!("从 CDN 获取 {} metadata: {}", language, metadata_url); let client = reqwest::Client::builder() diff --git a/src-tauri/src/env_providers/scala.rs b/src-tauri/src/env_providers/scala.rs index d975617..3680506 100644 --- a/src-tauri/src/env_providers/scala.rs +++ b/src-tauri/src/env_providers/scala.rs @@ -55,7 +55,7 @@ impl ScalaEnvironmentProvider { fn get_default_install_dir() -> PathBuf { let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); - home_dir.join(".codeforge").join("scala") + home_dir.join(".codeforge").join("plugins").join("scala") } // 从缓存读取版本列表 diff --git a/src-tauri/src/execution.rs b/src-tauri/src/execution.rs index f0e38b5..c15e08d 100644 --- a/src-tauri/src/execution.rs +++ b/src-tauri/src/execution.rs @@ -3,6 +3,7 @@ use log::{error, info, warn}; use std::collections::HashMap; use std::fs; use std::io::{BufRead, BufReader}; +use std::path::PathBuf; use std::process::{Command, Stdio}; use std::sync::{Arc, OnceLock, mpsc}; use std::thread; @@ -34,6 +35,39 @@ fn init_task_manager() -> TaskManager { .clone() } +// 获取 .codeforge 缓存目录 +fn get_codeforge_cache_dir(language: &str) -> Result { + let home_dir = dirs::home_dir().ok_or("无法获取用户主目录")?; + let cache_dir = home_dir + .join(".codeforge") + .join("cache") + .join("plugins") + .join(language); + + // 确保目录存在 + fs::create_dir_all(&cache_dir).map_err(|e| format!("创建缓存目录失败: {}", e))?; + + Ok(cache_dir) +} + +// 检查是否应该过滤 stderr 行 +fn should_filter_stderr_line(language: &str, line: &str) -> bool { + match language { + "clojure" => { + // 过滤 Clojure 的常见警告信息 + line.contains("WARNING: Implicit use of clojure.main") + || line.contains("WARNING: name already refers to:") + } + "scala" => { + // 过滤 Scala 的编译信息 + line.contains("[0m[0m[33mCompiling project") + || line.contains("[0m[0m") + || line.starts_with("\u{001b}") + } + _ => false, + } +} + // 停止执行命令 #[tauri::command] pub async fn stop_execution(language: String) -> Result { @@ -80,13 +114,17 @@ pub async fn execute_code( .get_plugin(&request.language) .ok_or_else(|| format!("Unsupported language: {}", request.language))?; - let temp_dir = std::env::temp_dir(); - let file_name = format!( - "Codeforge_{}.{}", - request.language, - plugin.get_file_extension() - ); - let file_path = temp_dir.join(file_name.clone()); + // 使用 .codeforge/cache/plugin/ 目录 + let temp_dir = get_codeforge_cache_dir(&request.language)?; + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + let file_work = format!("Codeforge_{}_{}", request.language, timestamp); + let work_dir = temp_dir.join(&file_work); + fs::create_dir_all(&work_dir).map_err(|e| format!("创建工作目录失败: {}", e))?; + let file_name = format!("{}.{}", file_work, plugin.get_file_extension()); + let file_path = work_dir.join(&file_name); // 写入代码到临时文件 fs::write(&file_path, &request.code) @@ -274,14 +312,17 @@ pub async fn execute_code( // 读取并发送 stderr while let Ok(line) = stderr_rx.try_recv() { stderr_lines.push(line.clone()); - let _ = app.emit( - "code-output", - serde_json::json!({ - "type": "stderr", - "content": line, - "language": request.language - }), - ); + // 过滤掉特定语言的警告信息 + if !should_filter_stderr_line(&request.language, &line) { + let _ = app.emit( + "code-output", + serde_json::json!({ + "type": "stderr", + "content": line, + "language": request.language + }), + ); + } } // 检查进程是否结束 @@ -301,14 +342,17 @@ pub async fn execute_code( } while let Ok(line) = stderr_rx.try_recv() { stderr_lines.push(line.clone()); - let _ = app.emit( - "code-output", - serde_json::json!({ - "type": "stderr", - "content": line, - "language": request.language - }), - ); + // 过滤掉特定语言的警告信息 + if !should_filter_stderr_line(&request.language, &line) { + let _ = app.emit( + "code-output", + serde_json::json!({ + "type": "stderr", + "content": line, + "language": request.language + }), + ); + } } let execution_time = start_time.elapsed().as_millis(); diff --git a/src-tauri/src/logger.rs b/src-tauri/src/logger.rs index c466c83..27bb4fd 100644 --- a/src-tauri/src/logger.rs +++ b/src-tauri/src/logger.rs @@ -107,11 +107,24 @@ fn get_effective_log_directory(app: &AppHandle) -> PathBuf { } // 获取默认日志目录 -fn get_default_log_directory(app: &AppHandle) -> PathBuf { - app.path() - .app_data_dir() - .expect("Failed to get app data dir") - .join("logs") +fn get_default_log_directory(_app: &AppHandle) -> PathBuf { + let home_dir = dirs::home_dir().expect("Failed to get home directory"); + let log_dir = home_dir.join(".codeforge").join("logs"); + + // 确保目录存在 + if !log_dir.exists() { + if let Err(e) = fs::create_dir_all(&log_dir) { + eprintln!("Failed to create log directory: {}", e); + // 如果创建失败,回退到应用数据目录 + return _app + .path() + .app_data_dir() + .expect("Failed to get app data dir") + .join("logs"); + } + } + + log_dir } // 公共函数,供其他模块调用 diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 37edb26..d84fe46 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,6 +3,7 @@ windows_subsystem = "windows" )] +mod cache; mod config; mod env_commands; mod env_manager; @@ -17,6 +18,7 @@ mod setup; mod update; mod utils; +use crate::cache::{clear_all_cache, clear_plugins_cache, get_cache_info}; use crate::env_commands::{ EnvironmentManagerState, download_and_install_version, get_environment_info, get_supported_environment_languages, switch_environment_version, uninstall_environment_version, @@ -107,6 +109,10 @@ fn main() { get_app_config, update_app_config, get_config_path, + // 缓存相关命令 + get_cache_info, + clear_plugins_cache, + clear_all_cache, // 更新相关命令 check_for_updates, start_update, diff --git a/src/components/Settings.vue b/src/components/Settings.vue index bfb902b..7edddef 100644 --- a/src/components/Settings.vue +++ b/src/components/Settings.vue @@ -25,6 +25,11 @@ + + + @@ -37,6 +42,7 @@ import General from './setting/General.vue' import Language from './setting/Language.vue' import Editor from './setting/Editor.vue' import Network from './setting/Network.vue' +import Cache from './setting/Cache.vue' import { useSettings } from '../composables/useSettings.ts' const emit = defineEmits<{ diff --git a/src/components/setting/Cache.vue b/src/components/setting/Cache.vue new file mode 100644 index 0000000..8ba059e --- /dev/null +++ b/src/components/setting/Cache.vue @@ -0,0 +1,198 @@ + + + diff --git a/src/components/setting/Network.vue b/src/components/setting/Network.vue index e5b2af4..a1effb8 100644 --- a/src/components/setting/Network.vue +++ b/src/components/setting/Network.vue @@ -75,9 +75,9 @@

CDN 镜像说明

  • CDN 镜像用于加速环境安装包的下载
  • -
  • URL 格式:{base_url}/{language}/{version}/{filename}
  • -
  • 例如:http://cdn.global.devlive.top/clojure/1.12.4.1582/clojure-tools-1.12.4.1582.tar.gz -
  • + + +
  • 启用自动回退后,CDN 下载失败会自动使用 GitHub 官方源
  • 关闭自动回退后,CDN 下载失败将直接报错,不会尝试其他源
diff --git a/src/composables/useLogDirectory.ts b/src/composables/useLogDirectory.ts index 17812f1..6129542 100644 --- a/src/composables/useLogDirectory.ts +++ b/src/composables/useLogDirectory.ts @@ -2,6 +2,7 @@ import { ref } from 'vue' import { invoke } from '@tauri-apps/api/core' import { open as openDialog } from '@tauri-apps/plugin-dialog' import { openPath } from '@tauri-apps/plugin-opener' +import { join } from '@tauri-apps/api/path' import { useToast } from '../plugins/toast' export function useLogDirectory(emit: any) @@ -97,7 +98,7 @@ export function useLogDirectory(emit: any) const openLogFile = async (filename: string) => { try { - const logPath = `${ currentLogDir.value }/${ filename }` + const logPath = await join(currentLogDir.value, filename) await openPath(logPath) } catch (error) { diff --git a/src/composables/useSettings.ts b/src/composables/useSettings.ts index 91dc8ff..866eee6 100644 --- a/src/composables/useSettings.ts +++ b/src/composables/useSettings.ts @@ -1,5 +1,5 @@ import { nextTick, ref } from 'vue' -import { BracesIcon, CodeIcon, Globe, ShieldIcon } from 'lucide-vue-next' +import { BracesIcon, CodeIcon, Database, Globe, ShieldIcon } from 'lucide-vue-next' export function useSettings(emit: any) { @@ -12,7 +12,8 @@ export function useSettings(emit: any) { key: 'general', label: '通用', icon: ShieldIcon }, { key: 'editor', label: '编辑器', icon: CodeIcon }, { key: 'language', label: '语言', icon: BracesIcon }, - { key: 'network', label: '网络', icon: Globe } + { key: 'network', label: '网络', icon: Globe }, + { key: 'cache', label: '缓存', icon: Database } ] const handleEditorSettingsChanged = (config: any) => {