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}