Skip to main content

mimick/
app_context.rs

1//! Consolidated application context passed through the UI and background services.
2//!
3//! Replaces the growing list of individual `Arc<T>` parameters that were previously
4//! threaded through `build_settings_window()` and `open_settings_if_needed()`.
5
6use parking_lot::{Mutex, RwLock};
7use std::collections::HashMap;
8use std::sync::Arc;
9use std::sync::atomic::AtomicBool;
10use std::time::{Duration, Instant};
11use tokio::sync::mpsc::UnboundedSender;
12
13use crate::api_client::ImmichApiClient;
14use crate::config::{Config, WatchPathEntry};
15use crate::library::state::LibraryState;
16use crate::library::thumbnail_cache::ThumbnailCache;
17use crate::monitor::MonitorHandle;
18use crate::queue_manager::QueueManager;
19use crate::state_manager::AppState;
20use crate::sync_index::ShardedSyncIndex;
21
22/// TTL'd set of paths Mimick itself just modified, used to suppress the
23/// filesystem events those modifications cause.
24#[derive(Default)]
25pub struct RecentSelfPaths {
26    /// Mutex-wrapped map of self-modified paths to the instant they were modified.
27    inner: Mutex<HashMap<String, Instant>>,
28}
29
30impl RecentSelfPaths {
31    /// Time-to-live after which self-modified paths are expired.
32    const TTL: Duration = Duration::from_secs(60);
33
34    /// Record a path as being modified by the application itself.
35    pub fn mark(&self, path: &str) {
36        let mut map = self.inner.lock();
37        map.retain(|_, t| t.elapsed() < Self::TTL);
38        map.insert(path.to_string(), Instant::now());
39    }
40
41    /// Check if a path is within its TTL and consume the record if present.
42    pub fn consume(&self, path: &str) -> bool {
43        let mut map = self.inner.lock();
44        map.retain(|_, t| t.elapsed() < Self::TTL);
45        map.remove(path).is_some()
46    }
47}
48
49/// Per-album reconcile lock. Prevents the periodic poller and a manual sync
50/// click from racing each other into duplicate downloads / duplicate trash.
51#[derive(Default)]
52pub struct ReconcileLocks {
53    /// Track of album IDs currently undergoing reconciliation.
54    inner: Mutex<std::collections::HashSet<String>>,
55}
56
57impl ReconcileLocks {
58    /// Attempt to acquire a lock for the given album, returning a guard on success.
59    pub fn try_acquire(self: &Arc<Self>, album_id: String) -> Option<ReconcileGuard> {
60        let mut set = self.inner.lock();
61        if !set.insert(album_id.clone()) {
62            return None;
63        }
64        Some(ReconcileGuard {
65            locks: self.clone(),
66            album_id,
67        })
68    }
69}
70
71/// Active lock guard that frees the album lock when dropped.
72pub struct ReconcileGuard {
73    /// Reference back to the parent locks list.
74    locks: Arc<ReconcileLocks>,
75    /// Unique identifier of the locked album.
76    album_id: String,
77}
78
79impl Drop for ReconcileGuard {
80    fn drop(&mut self) {
81        self.locks.inner.lock().remove(&self.album_id);
82    }
83}
84
85/// Two-tick deletion confirmation. For mass deletions, require the same
86/// asset to be missing across two consecutive reconciler observations
87/// before trashing — defends against transient server-side stale reads.
88#[derive(Default)]
89pub struct PendingDeletions {
90    /// Number of observations recorded per asset ID.
91    inner: Mutex<HashMap<String, u32>>,
92}
93
94impl PendingDeletions {
95    /// Number of consecutive confirmations required to execute trashing.
96    pub const REQUIRED_CONFIRMATIONS: u32 = 2;
97
98    /// Increment the verification count for an asset and return if target threshold is reached.
99    pub fn confirm(&self, key: &str) -> bool {
100        let mut map = self.inner.lock();
101        let entry = map.entry(key.to_string()).or_insert(0);
102        *entry += 1;
103        *entry >= Self::REQUIRED_CONFIRMATIONS
104    }
105
106    /// Clear deletion confirmation history for a specific asset ID.
107    pub fn clear(&self, key: &str) {
108        self.inner.lock().remove(key);
109    }
110}
111
112/// Shared application context holding all dependency handles that UI and background
113/// tasks need. Wrapped in `Arc` at construction time so it can be cloned cheaply.
114pub struct AppContext {
115    /// Thread-safe configuration data.
116    pub config: Arc<RwLock<Config>>,
117    /// Thread-safe application state metrics.
118    pub state: Arc<Mutex<AppState>>,
119    /// Shared Immich API client handle.
120    pub api_client: Arc<ImmichApiClient>,
121    /// System upload and download worker queue coordinator.
122    pub queue_manager: Arc<QueueManager>,
123    /// Native file monitor handle.
124    pub monitor_handle: Arc<MonitorHandle>,
125    /// Concurrent database state check index.
126    pub sync_index: Arc<ShardedSyncIndex>,
127    /// Thread-safe active watched paths lists.
128    pub live_watch_paths: Arc<Mutex<Vec<WatchPathEntry>>>,
129    /// Channel sender to trigger manual synchronization catch-ups.
130    pub sync_now_tx: UnboundedSender<()>,
131    /// LRU thumbnail image cache coordinator.
132    pub thumbnail_cache: Arc<ThumbnailCache>,
133    /// GTK library view loading state container.
134    pub library_state: Arc<Mutex<LibraryState>>,
135    /// True if timeline page views are currently active.
136    pub library_timeline_active: AtomicBool,
137    /// Authenticated user unique identifier, fetched at bootstrap.
138    pub current_user_id: Arc<Mutex<Option<String>>>,
139    /// Tracking lists of deletions requested by Mimick itself.
140    pub expected_self_deletions: Arc<RecentSelfPaths>,
141    /// Tracking lists of files downloaded by Mimick itself.
142    pub expected_self_downloads: Arc<RecentSelfPaths>,
143    /// Active locks to prevent multi-sync conflicts.
144    pub reconcile_locks: Arc<ReconcileLocks>,
145    /// Deletions waiting to be confirmed across sync sweeps.
146    pub pending_deletions: Arc<PendingDeletions>,
147}