- Domaine: port ManagedTempStore + modèle (handle opaque, kind Directory/File, état Active/Released, allocation/lease, expiry). - Application: use cases Allocate/Resolve/Release/GcManagedTemp, horodatés via Clock. - Infrastructure: impl FS FsManagedTempStore sous app_data_dir/managed-tmp (root global inter-projets, métadonnées par handle, GC des leases expirés/libérés). - Exposition MCP contrôlée (ManagedTempToolProvider, catalogue) + câblage sandbox: les agents résolvent un handle au lieu d'inventer un path libre. - Tests: application/infrastructure managed_temp.
173 lines
6.7 KiB
Rust
173 lines
6.7 KiB
Rust
//! Tauri-managed application state.
|
|
//!
|
|
//! The non-Tauri composition root lives in `backend::BackendCore`. This type is
|
|
//! the desktop driving-adapter envelope: it adds Tauri transport bridges and
|
|
//! OS-window focus state, then dereferences to the shared core so existing
|
|
//! commands keep their field/method access unchanged.
|
|
|
|
use std::collections::HashMap;
|
|
use std::ops::Deref;
|
|
use std::path::PathBuf;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use backend::BackendCore;
|
|
use domain::PersistedPluginLayoutWindow;
|
|
use infrastructure::{ManagedTempToolProvider, TemplateToolProvider, TicketToolProvider};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::chat::ChatBridge;
|
|
use crate::embedded_server::EmbeddedServerController;
|
|
use crate::managed_temp::AppManagedTempToolProvider;
|
|
use crate::pty::PtyBridge;
|
|
use crate::templates::AppTemplateToolProvider;
|
|
use crate::tickets::AppTicketToolProvider;
|
|
|
|
pub use backend::ResumeContext;
|
|
|
|
/// Focused project snapshot shared with detached panel windows.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct FocusedProjectDto {
|
|
/// Project id as a string.
|
|
pub id: String,
|
|
/// Project display name.
|
|
pub name: String,
|
|
/// Absolute project root.
|
|
pub root: String,
|
|
}
|
|
|
|
/// Desktop adapter state managed by Tauri.
|
|
pub struct AppState {
|
|
core: Arc<BackendCore>,
|
|
/// Generic PTY to Tauri Channel bridge registry.
|
|
pub pty_bridge: Arc<PtyBridge>,
|
|
/// Structured reply to Tauri Channel bridge registry.
|
|
pub chat_bridge: Arc<ChatBridge>,
|
|
/// Desktop-owned embedded HTTP server lifecycle manager.
|
|
pub embedded_server: Arc<EmbeddedServerController>,
|
|
/// Project currently focused by the main window; panel-only windows follow it.
|
|
focused_project: Mutex<Option<FocusedProjectDto>>,
|
|
/// Detached plugin-layout surfaces keyed by Tauri window label.
|
|
plugin_window_surfaces: Mutex<HashMap<String, PersistedPluginLayoutWindow>>,
|
|
}
|
|
|
|
impl AppState {
|
|
/// Builds the shared backend core and wraps it with desktop-only transport
|
|
/// state. `app_data_dir` is resolved by the Tauri adapter before crossing
|
|
/// into the backend core.
|
|
#[must_use]
|
|
pub fn build(app_data_dir: PathBuf) -> Self {
|
|
Self::build_with_resource_dir(app_data_dir, None)
|
|
}
|
|
|
|
/// Builds the shared backend core with the Tauri bundle resource directory
|
|
/// available to desktop-only adapters.
|
|
#[must_use]
|
|
pub fn build_with_resource_dir(app_data_dir: PathBuf, resource_dir: Option<PathBuf>) -> Self {
|
|
let core = Arc::new(BackendCore::build(app_data_dir.clone()));
|
|
core.ticket_tool_binder
|
|
.bind(Arc::new(AppTicketToolProvider {
|
|
create: Arc::clone(&core.create_issue),
|
|
read: Arc::clone(&core.read_issue),
|
|
list: Arc::clone(&core.list_issues),
|
|
update: Arc::clone(&core.update_issue),
|
|
bulk_update_status: Arc::clone(&core.bulk_update_issue_status),
|
|
bulk_update_priority: Arc::clone(&core.bulk_update_issue_priority),
|
|
bulk_delete: Arc::clone(&core.bulk_delete_issues),
|
|
read_carnet: Arc::clone(&core.read_issue_carnet),
|
|
update_carnet: Arc::clone(&core.update_issue_carnet),
|
|
add_attachment: Arc::clone(&core.add_issue_attachment),
|
|
read_attachment: Arc::clone(&core.read_issue_attachment),
|
|
mark_attachment_summarized: Arc::clone(&core.mark_issue_attachment_summarized),
|
|
link: Arc::clone(&core.link_issues),
|
|
unlink: Arc::clone(&core.unlink_issues),
|
|
list_sprints: Arc::clone(&core.list_sprints),
|
|
}) as Arc<dyn TicketToolProvider>);
|
|
core.template_tool_binder
|
|
.bind(Arc::new(AppTemplateToolProvider {
|
|
create: Arc::clone(&core.create_template),
|
|
read: Arc::clone(&core.read_template),
|
|
list: Arc::clone(&core.list_templates),
|
|
update: Arc::clone(&core.update_template),
|
|
delete: Arc::clone(&core.delete_template),
|
|
}) as Arc<dyn TemplateToolProvider>);
|
|
core.managed_temp_tool_binder
|
|
.bind(Arc::new(AppManagedTempToolProvider {
|
|
allocate: Arc::clone(&core.allocate_managed_temp),
|
|
resolve: Arc::clone(&core.resolve_managed_temp),
|
|
release: Arc::clone(&core.release_managed_temp),
|
|
gc: Arc::clone(&core.gc_managed_temp),
|
|
}) as Arc<dyn ManagedTempToolProvider>);
|
|
|
|
Self {
|
|
core,
|
|
pty_bridge: Arc::new(PtyBridge::new()),
|
|
chat_bridge: Arc::new(ChatBridge::new()),
|
|
embedded_server: Arc::new(EmbeddedServerController::with_resource_dir(
|
|
app_data_dir,
|
|
resource_dir,
|
|
)),
|
|
focused_project: Mutex::new(None),
|
|
plugin_window_surfaces: Mutex::new(HashMap::new()),
|
|
}
|
|
}
|
|
|
|
/// Clones the shared backend core for adapter components that must share
|
|
/// the desktop composition root.
|
|
#[must_use]
|
|
pub fn core(&self) -> Arc<BackendCore> {
|
|
Arc::clone(&self.core)
|
|
}
|
|
|
|
/// Updates the focused project state. `None` means no project is focused/open.
|
|
pub fn set_focused_project(&self, project: Option<FocusedProjectDto>) {
|
|
*self
|
|
.focused_project
|
|
.lock()
|
|
.expect("focused project mutex poisoned") = project;
|
|
}
|
|
|
|
/// Returns the current focused project, if any.
|
|
#[must_use]
|
|
pub fn get_focused_project(&self) -> Option<FocusedProjectDto> {
|
|
self.focused_project
|
|
.lock()
|
|
.expect("focused project mutex poisoned")
|
|
.clone()
|
|
}
|
|
|
|
/// Records the plugin layout surface hosted by a detached window.
|
|
pub fn set_plugin_window_surface(&self, label: String, surface: PersistedPluginLayoutWindow) {
|
|
self.plugin_window_surfaces
|
|
.lock()
|
|
.expect("plugin window surface mutex poisoned")
|
|
.insert(label, surface);
|
|
}
|
|
|
|
/// Reads the plugin layout surface hosted by a detached window.
|
|
#[must_use]
|
|
pub fn get_plugin_window_surface(&self, label: &str) -> Option<PersistedPluginLayoutWindow> {
|
|
self.plugin_window_surfaces
|
|
.lock()
|
|
.expect("plugin window surface mutex poisoned")
|
|
.get(label)
|
|
.cloned()
|
|
}
|
|
|
|
/// Removes a detached plugin layout surface.
|
|
pub fn clear_plugin_window_surface(&self, label: &str) {
|
|
self.plugin_window_surfaces
|
|
.lock()
|
|
.expect("plugin window surface mutex poisoned")
|
|
.remove(label);
|
|
}
|
|
}
|
|
|
|
impl Deref for AppState {
|
|
type Target = BackendCore;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.core
|
|
}
|
|
}
|