brows3r_lib/commands/locks_cmd.rs
1//! Tauri commands for the resource lock registry.
2//!
3//! # Commands
4//!
5//! - `locks_list` — return active locks, optionally filtered by scope.
6//! - `lock_release_stale` — manual last-resort override to release a specific
7//! lock by ID (e.g. for a stuck operation).
8
9use tauri::State;
10
11use crate::{
12 error::AppError,
13 ids::{BucketId, ObjectKey, ProfileId},
14 locks::{LockId, LockRegistryHandle, LockScope, ResourceLock},
15};
16
17// ---------------------------------------------------------------------------
18// LockScopeDto — IPC-friendly version of LockScope
19// ---------------------------------------------------------------------------
20
21/// IPC-friendly (camelCase) mirror of `LockScope` used as a command argument.
22///
23/// The frontend passes `{ profile, bucket?, prefix?, key? }` in camelCase;
24/// this DTO converts to the domain type.
25#[derive(Debug, serde::Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct LockScopeDto {
28 pub profile: ProfileId,
29 pub bucket: Option<BucketId>,
30 pub prefix: Option<String>,
31 pub key: Option<ObjectKey>,
32}
33
34impl From<LockScopeDto> for LockScope {
35 fn from(dto: LockScopeDto) -> Self {
36 LockScope {
37 profile: dto.profile,
38 bucket: dto.bucket,
39 prefix: dto.prefix,
40 key: dto.key,
41 }
42 }
43}
44
45// ---------------------------------------------------------------------------
46// locks_list
47// ---------------------------------------------------------------------------
48
49/// Return all active locks, optionally filtered to those whose scope
50/// intersects `scope`.
51///
52/// When `scope` is `None` every active lock is returned.
53#[tauri::command]
54pub async fn locks_list(
55 scope: Option<LockScopeDto>,
56 registry: State<'_, LockRegistryHandle>,
57) -> Result<Vec<ResourceLock>, AppError> {
58 let filter = scope.map(LockScope::from);
59 Ok(registry.inner().list(filter.as_ref()))
60}
61
62// ---------------------------------------------------------------------------
63// lock_release_stale
64// ---------------------------------------------------------------------------
65
66/// Manually release a specific lock by ID.
67///
68/// This is a last-resort override (e.g. to unstick a crashed operation).
69/// The caller is responsible for understanding the consequences.
70///
71/// Returns `AppError::NotFound` if the lock does not exist.
72#[tauri::command]
73pub async fn lock_release_stale(
74 lock_id: LockId,
75 registry: State<'_, LockRegistryHandle>,
76) -> Result<(), AppError> {
77 // We discard the returned lock; the caller does not need the scope here.
78 registry.inner().release(&lock_id)?;
79 Ok(())
80}