后端通用错误处理
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::app_error::{AppCommandError, AppErrorCode};
|
||||
use crate::db::entities::conversation;
|
||||
use crate::db::service::{conversation_service, folder_service, import_service};
|
||||
use crate::db::AppDatabase;
|
||||
@@ -8,7 +9,7 @@ use crate::parsers::claude::ClaudeParser;
|
||||
use crate::parsers::codex::CodexParser;
|
||||
use crate::parsers::gemini::GeminiParser;
|
||||
use crate::parsers::opencode::OpenCodeParser;
|
||||
use crate::parsers::{path_eq_for_matching, AgentParser};
|
||||
use crate::parsers::{path_eq_for_matching, AgentParser, ParseError};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_folder_conversations(
|
||||
@@ -18,10 +19,10 @@ pub async fn list_folder_conversations(
|
||||
search: Option<String>,
|
||||
sort_by: Option<String>,
|
||||
status: Option<String>,
|
||||
) -> Result<Vec<DbConversationSummary>, String> {
|
||||
) -> Result<Vec<DbConversationSummary>, AppCommandError> {
|
||||
conversation_service::list_by_folder(&db.conn, folder_id, agent_type, search, sort_by, status)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(AppCommandError::from)
|
||||
}
|
||||
|
||||
/// Synchronous implementation shared by list_conversations, list_folders, and get_stats.
|
||||
@@ -30,7 +31,7 @@ fn list_conversations_sync(
|
||||
search: Option<String>,
|
||||
sort_by: Option<String>,
|
||||
folder_path: Option<String>,
|
||||
) -> Result<Vec<ConversationSummary>, String> {
|
||||
) -> Vec<ConversationSummary> {
|
||||
let mut all_conversations = Vec::new();
|
||||
let mut seen_keys = HashSet::new();
|
||||
|
||||
@@ -103,7 +104,7 @@ fn list_conversations_sync(
|
||||
_ => all_conversations.sort_by(|a, b| b.started_at.cmp(&a.started_at)), // default: newest first
|
||||
}
|
||||
|
||||
Ok(all_conversations)
|
||||
all_conversations
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -112,70 +113,89 @@ pub async fn list_conversations(
|
||||
search: Option<String>,
|
||||
sort_by: Option<String>,
|
||||
folder_path: Option<String>,
|
||||
) -> Result<Vec<ConversationSummary>, String> {
|
||||
) -> Result<Vec<ConversationSummary>, AppCommandError> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
list_conversations_sync(agent_type, search, sort_by, folder_path)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(|e| {
|
||||
AppCommandError::new(AppErrorCode::Unknown, "Failed to list conversations")
|
||||
.with_detail(e.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_conversation(
|
||||
agent_type: AgentType,
|
||||
conversation_id: String,
|
||||
) -> Result<ConversationDetail, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
) -> Result<ConversationDetail, AppCommandError> {
|
||||
tokio::task::spawn_blocking(move || -> Result<ConversationDetail, AppCommandError> {
|
||||
let parser: Box<dyn AgentParser> = match agent_type {
|
||||
AgentType::ClaudeCode => Box::new(ClaudeParser::new()),
|
||||
AgentType::Codex => Box::new(CodexParser::new()),
|
||||
AgentType::OpenCode => Box::new(OpenCodeParser::new()),
|
||||
AgentType::Gemini => Box::new(GeminiParser::new()),
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"conversation parsing not supported for {agent_type}"
|
||||
))
|
||||
return Err(
|
||||
AppCommandError::new(
|
||||
AppErrorCode::InvalidInput,
|
||||
"Conversation parsing is not supported for this agent",
|
||||
)
|
||||
.with_detail(format!("agent_type={agent_type}")),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
parser
|
||||
.get_conversation(&conversation_id)
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(parse_error_to_app_error)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(|e| {
|
||||
AppCommandError::new(AppErrorCode::Unknown, "Failed to load conversation")
|
||||
.with_detail(e.to_string())
|
||||
})?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_folders() -> Result<Vec<FolderInfo>, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let all_conversations = list_conversations_sync(None, None, None, None)?;
|
||||
pub async fn list_folders() -> Result<Vec<FolderInfo>, AppCommandError> {
|
||||
tokio::task::spawn_blocking(move || -> Result<Vec<FolderInfo>, AppCommandError> {
|
||||
let all_conversations = list_conversations_sync(None, None, None, None);
|
||||
Ok(compute_folders(&all_conversations))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(|e| {
|
||||
AppCommandError::new(AppErrorCode::Unknown, "Failed to list folders")
|
||||
.with_detail(e.to_string())
|
||||
})?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_stats() -> Result<AgentStats, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let all_conversations = list_conversations_sync(None, None, None, None)?;
|
||||
compute_stats(&all_conversations)
|
||||
pub async fn get_stats() -> Result<AgentStats, AppCommandError> {
|
||||
tokio::task::spawn_blocking(move || -> Result<AgentStats, AppCommandError> {
|
||||
let all_conversations = list_conversations_sync(None, None, None, None);
|
||||
Ok(compute_stats(&all_conversations))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(|e| {
|
||||
AppCommandError::new(AppErrorCode::Unknown, "Failed to compute conversation stats")
|
||||
.with_detail(e.to_string())
|
||||
})?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_sidebar_data() -> Result<SidebarData, String> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let all_conversations = list_conversations_sync(None, None, None, None)?;
|
||||
pub async fn get_sidebar_data() -> Result<SidebarData, AppCommandError> {
|
||||
tokio::task::spawn_blocking(move || -> Result<SidebarData, AppCommandError> {
|
||||
let all_conversations = list_conversations_sync(None, None, None, None);
|
||||
let folders = compute_folders(&all_conversations);
|
||||
let stats = compute_stats(&all_conversations)?;
|
||||
let stats = compute_stats(&all_conversations);
|
||||
Ok(SidebarData { folders, stats })
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(|e| {
|
||||
AppCommandError::new(AppErrorCode::Unknown, "Failed to build sidebar data")
|
||||
.with_detail(e.to_string())
|
||||
})?
|
||||
}
|
||||
|
||||
fn compute_folders(all_conversations: &[ConversationSummary]) -> Vec<FolderInfo> {
|
||||
@@ -215,30 +235,33 @@ fn compute_folders(all_conversations: &[ConversationSummary]) -> Vec<FolderInfo>
|
||||
pub async fn import_local_conversations(
|
||||
db: tauri::State<'_, AppDatabase>,
|
||||
folder_id: i32,
|
||||
) -> Result<ImportResult, String> {
|
||||
) -> Result<ImportResult, AppCommandError> {
|
||||
let folder = folder_service::get_folder_by_id(&db.conn, folder_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.ok_or_else(|| format!("Folder not found: {folder_id}"))?;
|
||||
.map_err(AppCommandError::from)?
|
||||
.ok_or_else(|| {
|
||||
AppCommandError::new(AppErrorCode::NotFound, "Folder not found")
|
||||
.with_detail(format!("folder_id={folder_id}"))
|
||||
})?;
|
||||
|
||||
import_service::import_local_conversations(&db.conn, folder_id, &folder.path)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(AppCommandError::from)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_folder_conversation(
|
||||
db: tauri::State<'_, AppDatabase>,
|
||||
conversation_id: i32,
|
||||
) -> Result<DbConversationDetail, String> {
|
||||
) -> Result<DbConversationDetail, AppCommandError> {
|
||||
let summary = conversation_service::get_by_id(&db.conn, conversation_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
let (turns, session_stats) = if let Some(ref ext_id) = summary.external_id {
|
||||
let at = summary.agent_type;
|
||||
let eid = ext_id.clone();
|
||||
tokio::task::spawn_blocking(move || -> Result<_, String> {
|
||||
tokio::task::spawn_blocking(move || -> Result<_, AppCommandError> {
|
||||
let parser: Box<dyn AgentParser> = match at {
|
||||
AgentType::ClaudeCode => Box::new(ClaudeParser::new()),
|
||||
AgentType::Codex => Box::new(CodexParser::new()),
|
||||
@@ -251,12 +274,17 @@ pub async fn get_folder_conversation(
|
||||
match parser.get_conversation(&eid) {
|
||||
Ok(d) => Ok((d.turns, d.session_stats)),
|
||||
Err(crate::parsers::ParseError::ConversationNotFound(_)) => Ok((vec![], None)),
|
||||
Err(e) => Err(e.to_string()),
|
||||
Err(e) => Err(parse_error_to_app_error(e)),
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(|e: String| e)?
|
||||
.map_err(|e| {
|
||||
AppCommandError::new(
|
||||
AppErrorCode::Unknown,
|
||||
"Failed to read conversation turns from session file",
|
||||
)
|
||||
.with_detail(e.to_string())
|
||||
})??
|
||||
} else {
|
||||
(vec![], None)
|
||||
};
|
||||
@@ -277,11 +305,11 @@ pub async fn create_conversation(
|
||||
folder_id: i32,
|
||||
agent_type: AgentType,
|
||||
title: Option<String>,
|
||||
) -> Result<i32, String> {
|
||||
) -> Result<i32, AppCommandError> {
|
||||
// Detect current git branch from the folder path
|
||||
let git_branch = if let Some(folder) = folder_service::get_folder_by_id(&db.conn, folder_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.map_err(AppCommandError::from)?
|
||||
{
|
||||
detect_git_branch(&folder.path).await
|
||||
} else {
|
||||
@@ -290,7 +318,7 @@ pub async fn create_conversation(
|
||||
|
||||
let model = conversation_service::create(&db.conn, folder_id, agent_type, title, git_branch)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(AppCommandError::from)?;
|
||||
Ok(model.id)
|
||||
}
|
||||
|
||||
@@ -318,12 +346,17 @@ pub async fn update_conversation_status(
|
||||
db: tauri::State<'_, AppDatabase>,
|
||||
conversation_id: i32,
|
||||
status: String,
|
||||
) -> Result<(), String> {
|
||||
let status_enum: conversation::ConversationStatus =
|
||||
serde_json::from_value(serde_json::Value::String(status)).map_err(|e| e.to_string())?;
|
||||
) -> Result<(), AppCommandError> {
|
||||
let status_enum: conversation::ConversationStatus = serde_json::from_value(
|
||||
serde_json::Value::String(status),
|
||||
)
|
||||
.map_err(|e| {
|
||||
AppCommandError::new(AppErrorCode::InvalidInput, "Invalid conversation status")
|
||||
.with_detail(e.to_string())
|
||||
})?;
|
||||
conversation_service::update_status(&db.conn, conversation_id, status_enum)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(AppCommandError::from)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -331,10 +364,10 @@ pub async fn update_conversation_title(
|
||||
db: tauri::State<'_, AppDatabase>,
|
||||
conversation_id: i32,
|
||||
title: String,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), AppCommandError> {
|
||||
conversation_service::update_title(&db.conn, conversation_id, title)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(AppCommandError::from)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -342,23 +375,23 @@ pub async fn update_conversation_external_id(
|
||||
db: tauri::State<'_, AppDatabase>,
|
||||
conversation_id: i32,
|
||||
external_id: String,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), AppCommandError> {
|
||||
conversation_service::update_external_id(&db.conn, conversation_id, external_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(AppCommandError::from)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_conversation(
|
||||
db: tauri::State<'_, AppDatabase>,
|
||||
conversation_id: i32,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), AppCommandError> {
|
||||
conversation_service::soft_delete(&db.conn, conversation_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
.map_err(AppCommandError::from)
|
||||
}
|
||||
|
||||
fn compute_stats(all_conversations: &[ConversationSummary]) -> Result<AgentStats, String> {
|
||||
fn compute_stats(all_conversations: &[ConversationSummary]) -> AgentStats {
|
||||
let mut total_messages: u32 = 0;
|
||||
let mut counts: HashMap<AgentType, u32> = HashMap::new();
|
||||
|
||||
@@ -376,9 +409,32 @@ fn compute_stats(all_conversations: &[ConversationSummary]) -> Result<AgentStats
|
||||
.collect();
|
||||
by_agent.sort_by(|a, b| b.conversation_count.cmp(&a.conversation_count));
|
||||
|
||||
Ok(AgentStats {
|
||||
AgentStats {
|
||||
total_conversations: all_conversations.len() as u32,
|
||||
total_messages,
|
||||
by_agent,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_error_to_app_error(error: ParseError) -> AppCommandError {
|
||||
match error {
|
||||
ParseError::ConversationNotFound(id) => {
|
||||
AppCommandError::new(AppErrorCode::NotFound, "Conversation not found")
|
||||
.with_detail(id)
|
||||
}
|
||||
ParseError::InvalidData(message) => {
|
||||
AppCommandError::new(AppErrorCode::InvalidInput, "Invalid conversation data")
|
||||
.with_detail(message)
|
||||
}
|
||||
ParseError::Io(err) => AppCommandError::new(AppErrorCode::IoError, "I/O operation failed")
|
||||
.with_detail(err.to_string()),
|
||||
ParseError::Json(err) => {
|
||||
AppCommandError::new(AppErrorCode::InvalidInput, "Failed to parse conversation file")
|
||||
.with_detail(err.to_string())
|
||||
}
|
||||
ParseError::Db(err) => {
|
||||
AppCommandError::new(AppErrorCode::DatabaseError, "Database operation failed")
|
||||
.with_detail(err.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tauri::State;
|
||||
|
||||
use crate::app_error::{AppCommandError, AppErrorCode};
|
||||
use crate::db::service::app_metadata_service;
|
||||
use crate::db::AppDatabase;
|
||||
use crate::models::{SystemLanguageSettings, SystemProxySettings};
|
||||
@@ -9,7 +10,9 @@ use crate::network::proxy;
|
||||
const SYSTEM_PROXY_SETTINGS_KEY: &str = "system_proxy_settings";
|
||||
const SYSTEM_LANGUAGE_SETTINGS_KEY: &str = "system_language_settings";
|
||||
|
||||
fn normalize_proxy_settings(settings: SystemProxySettings) -> Result<SystemProxySettings, String> {
|
||||
fn normalize_proxy_settings(
|
||||
settings: SystemProxySettings,
|
||||
) -> Result<SystemProxySettings, AppCommandError> {
|
||||
if !settings.enabled {
|
||||
let proxy_url = settings
|
||||
.proxy_url
|
||||
@@ -29,9 +32,17 @@ fn normalize_proxy_settings(settings: SystemProxySettings) -> Result<SystemProxy
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.ok_or_else(|| "proxy url is required when proxy is enabled".to_string())?;
|
||||
.ok_or_else(|| {
|
||||
AppCommandError::new(
|
||||
AppErrorCode::InvalidInput,
|
||||
"Proxy URL is required when proxy is enabled",
|
||||
)
|
||||
})?;
|
||||
|
||||
reqwest::Proxy::all(proxy_url).map_err(|e| format!("invalid proxy url: {e}"))?;
|
||||
reqwest::Proxy::all(proxy_url).map_err(|e| {
|
||||
AppCommandError::new(AppErrorCode::InvalidInput, "Invalid proxy URL")
|
||||
.with_detail(e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(SystemProxySettings {
|
||||
enabled: true,
|
||||
@@ -41,39 +52,49 @@ fn normalize_proxy_settings(settings: SystemProxySettings) -> Result<SystemProxy
|
||||
|
||||
pub(crate) async fn load_system_proxy_settings(
|
||||
conn: &DatabaseConnection,
|
||||
) -> Result<SystemProxySettings, String> {
|
||||
) -> Result<SystemProxySettings, AppCommandError> {
|
||||
let raw = app_metadata_service::get_value(conn, SYSTEM_PROXY_SETTINGS_KEY)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
let Some(raw) = raw else {
|
||||
return Ok(SystemProxySettings::default());
|
||||
};
|
||||
|
||||
let parsed = serde_json::from_str::<SystemProxySettings>(&raw)
|
||||
.map_err(|e| format!("failed to parse stored proxy settings: {e}"))?;
|
||||
let parsed = serde_json::from_str::<SystemProxySettings>(&raw).map_err(|e| {
|
||||
AppCommandError::new(
|
||||
AppErrorCode::InvalidInput,
|
||||
"Failed to parse stored proxy settings",
|
||||
)
|
||||
.with_detail(e.to_string())
|
||||
})?;
|
||||
normalize_proxy_settings(parsed)
|
||||
}
|
||||
|
||||
pub(crate) async fn load_system_language_settings(
|
||||
conn: &DatabaseConnection,
|
||||
) -> Result<SystemLanguageSettings, String> {
|
||||
) -> Result<SystemLanguageSettings, AppCommandError> {
|
||||
let raw = app_metadata_service::get_value(conn, SYSTEM_LANGUAGE_SETTINGS_KEY)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
let Some(raw) = raw else {
|
||||
return Ok(SystemLanguageSettings::default());
|
||||
};
|
||||
|
||||
serde_json::from_str::<SystemLanguageSettings>(&raw)
|
||||
.map_err(|e| format!("failed to parse stored language settings: {e}"))
|
||||
serde_json::from_str::<SystemLanguageSettings>(&raw).map_err(|e| {
|
||||
AppCommandError::new(
|
||||
AppErrorCode::InvalidInput,
|
||||
"Failed to parse stored language settings",
|
||||
)
|
||||
.with_detail(e.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_system_proxy_settings(
|
||||
db: State<'_, AppDatabase>,
|
||||
) -> Result<SystemProxySettings, String> {
|
||||
) -> Result<SystemProxySettings, AppCommandError> {
|
||||
load_system_proxy_settings(&db.conn).await
|
||||
}
|
||||
|
||||
@@ -81,22 +102,34 @@ pub async fn get_system_proxy_settings(
|
||||
pub async fn update_system_proxy_settings(
|
||||
settings: SystemProxySettings,
|
||||
db: State<'_, AppDatabase>,
|
||||
) -> Result<SystemProxySettings, String> {
|
||||
) -> Result<SystemProxySettings, AppCommandError> {
|
||||
let normalized = normalize_proxy_settings(settings)?;
|
||||
let serialized = serde_json::to_string(&normalized).map_err(|e| e.to_string())?;
|
||||
let serialized = serde_json::to_string(&normalized).map_err(|e| {
|
||||
AppCommandError::new(
|
||||
AppErrorCode::InvalidInput,
|
||||
"Failed to serialize proxy settings",
|
||||
)
|
||||
.with_detail(e.to_string())
|
||||
})?;
|
||||
|
||||
app_metadata_service::upsert_value(&db.conn, SYSTEM_PROXY_SETTINGS_KEY, &serialized)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
proxy::apply_system_proxy_settings(&normalized)?;
|
||||
proxy::apply_system_proxy_settings(&normalized).map_err(|e| {
|
||||
AppCommandError::new(
|
||||
AppErrorCode::ExternalCommandFailed,
|
||||
"Failed to apply system proxy settings",
|
||||
)
|
||||
.with_detail(e)
|
||||
})?;
|
||||
Ok(normalized)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_system_language_settings(
|
||||
db: State<'_, AppDatabase>,
|
||||
) -> Result<SystemLanguageSettings, String> {
|
||||
) -> Result<SystemLanguageSettings, AppCommandError> {
|
||||
load_system_language_settings(&db.conn).await
|
||||
}
|
||||
|
||||
@@ -104,12 +137,18 @@ pub async fn get_system_language_settings(
|
||||
pub async fn update_system_language_settings(
|
||||
settings: SystemLanguageSettings,
|
||||
db: State<'_, AppDatabase>,
|
||||
) -> Result<SystemLanguageSettings, String> {
|
||||
let serialized = serde_json::to_string(&settings).map_err(|e| e.to_string())?;
|
||||
) -> Result<SystemLanguageSettings, AppCommandError> {
|
||||
let serialized = serde_json::to_string(&settings).map_err(|e| {
|
||||
AppCommandError::new(
|
||||
AppErrorCode::InvalidInput,
|
||||
"Failed to serialize language settings",
|
||||
)
|
||||
.with_detail(e.to_string())
|
||||
})?;
|
||||
|
||||
app_metadata_service::upsert_value(&db.conn, SYSTEM_LANGUAGE_SETTINGS_KEY, &serialized)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Mutex;
|
||||
|
||||
use tauri::{AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};
|
||||
|
||||
use crate::app_error::AppCommandError;
|
||||
use crate::app_error::{AppCommandError, AppErrorCode};
|
||||
use crate::db::AppDatabase;
|
||||
use crate::models::FolderHistoryEntry;
|
||||
|
||||
@@ -152,7 +152,7 @@ fn resolve_settings_target(section: Option<&str>, agent_type: Option<&str>) -> S
|
||||
pub async fn list_open_folders(
|
||||
app: AppHandle,
|
||||
db: tauri::State<'_, AppDatabase>,
|
||||
) -> Result<Vec<FolderHistoryEntry>, String> {
|
||||
) -> Result<Vec<FolderHistoryEntry>, AppCommandError> {
|
||||
let windows = app.webview_windows();
|
||||
let mut folder_ids: Vec<i32> = Vec::new();
|
||||
|
||||
@@ -166,7 +166,7 @@ pub async fn list_open_folders(
|
||||
|
||||
let all_folders = crate::db::service::folder_service::list_folders(&db.conn)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(AppCommandError::from)?;
|
||||
|
||||
let open_folders: Vec<FolderHistoryEntry> = all_folders
|
||||
.into_iter()
|
||||
@@ -177,19 +177,27 @@ pub async fn list_open_folders(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn focus_folder_window(app: AppHandle, folder_id: i32) -> Result<(), String> {
|
||||
pub async fn focus_folder_window(app: AppHandle, folder_id: i32) -> Result<(), AppCommandError> {
|
||||
let windows = app.webview_windows();
|
||||
for (label, window) in &windows {
|
||||
if label.starts_with("folder-") {
|
||||
if let Some(id) = get_folder_id_from_window(window) {
|
||||
if id == folder_id {
|
||||
window.set_focus().map_err(|e| e.to_string())?;
|
||||
window.set_focus().map_err(|e| {
|
||||
AppCommandError::window("Failed to focus folder window", e.to_string())
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(format!("No open window for folder {}", folder_id))
|
||||
Err(
|
||||
AppCommandError::new(
|
||||
AppErrorCode::NotFound,
|
||||
format!("No open window for folder {folder_id}"),
|
||||
)
|
||||
.with_detail(format!("folder_id={folder_id}")),
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -231,24 +239,34 @@ pub async fn open_commit_window(
|
||||
db: tauri::State<'_, AppDatabase>,
|
||||
state: tauri::State<'_, CommitWindowState>,
|
||||
folder_id: i32,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<(), AppCommandError> {
|
||||
let owner_label = window.label().to_string();
|
||||
let label = format!("commit-{folder_id}");
|
||||
|
||||
if let Some(existing) = app.get_webview_window(&label) {
|
||||
if let Some(owner_window) = app.get_webview_window(&owner_label) {
|
||||
owner_window.set_enabled(false).map_err(|e| e.to_string())?;
|
||||
owner_window.set_enabled(false).map_err(|e| {
|
||||
AppCommandError::window("Failed to disable owner window", e.to_string())
|
||||
})?;
|
||||
}
|
||||
state.set_owner(label.clone(), owner_label);
|
||||
let _ = existing.unminimize();
|
||||
existing.set_focus().map_err(|e| e.to_string())?;
|
||||
existing.set_focus().map_err(|e| {
|
||||
AppCommandError::window("Failed to focus commit window", e.to_string())
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let folder = crate::db::service::folder_service::get_folder_by_id(&db.conn, folder_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.ok_or_else(|| format!("Folder {} not found", folder_id))?;
|
||||
.map_err(AppCommandError::from)?
|
||||
.ok_or_else(|| {
|
||||
AppCommandError::new(
|
||||
AppErrorCode::NotFound,
|
||||
format!("Folder {folder_id} not found"),
|
||||
)
|
||||
.with_detail(format!("folder_id={folder_id}"))
|
||||
})?;
|
||||
|
||||
let url = WebviewUrl::App(format!("commit?folderId={folder_id}").into());
|
||||
let builder = WebviewWindowBuilder::new(&app, &label, url)
|
||||
@@ -259,16 +277,21 @@ pub async fn open_commit_window(
|
||||
.center();
|
||||
let commit_window = apply_platform_window_style(builder)
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| AppCommandError::window("Failed to open commit window", e.to_string()))?;
|
||||
ensure_windows_undecorated(&commit_window);
|
||||
if let Some(owner_window) = app.get_webview_window(&owner_label) {
|
||||
if let Err(err) = owner_window.set_enabled(false) {
|
||||
let _ = commit_window.close();
|
||||
return Err(err.to_string());
|
||||
return Err(AppCommandError::window(
|
||||
"Failed to disable owner window",
|
||||
err.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
state.set_owner(label, owner_label);
|
||||
commit_window.set_focus().map_err(|e| e.to_string())?;
|
||||
commit_window
|
||||
.set_focus()
|
||||
.map_err(|e| AppCommandError::window("Failed to focus commit window", e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -359,7 +382,7 @@ pub fn restore_window_after_commit(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_welcome_window(app: &AppHandle) -> Result<(), String> {
|
||||
pub fn open_welcome_window(app: &AppHandle) -> Result<(), AppCommandError> {
|
||||
if let Some(existing) = app.get_webview_window("welcome") {
|
||||
ensure_windows_undecorated(&existing);
|
||||
return Ok(());
|
||||
@@ -372,7 +395,7 @@ pub fn open_welcome_window(app: &AppHandle) -> Result<(), String> {
|
||||
.center();
|
||||
let welcome_window = apply_platform_window_style(builder)
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
.map_err(|e| AppCommandError::window("Failed to open welcome window", e.to_string()))?;
|
||||
ensure_windows_undecorated(&welcome_window);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user