Repository navigation
Architecture
This document describes the high-level architecture of mimick, a Linux desktop daemon and client for syncing media to Immich.
The application is a single-process Rust daemon using the GTK4/Tokio runtime with application ID dev.nicx.mimick.
graph TD
User[User] -->|Clicks Library| Tray[System Tray Icon - ksni]
Tray -->|tokio::sync::watch channel| Main[GTK Main Loop - main.rs]
subgraph Daemon [Primary Process]
Main --> Monitor[File Monitor - notify crate]
Main --> QueueManager[Queue Manager]
Main --> SharedState["Arc<Mutex<AppState>> - in memory"]
Main --> RuntimeEnv[Runtime Environment Checks]
Main --> StartupScan[Startup Catch-Up Scan]
Main --> RemoteSync[Remote Album Reconciler - 5 min loop]
Monitor -->|File Events + SHA-1| QueueManager
StartupScan -->|Unsynced files| QueueManager
RemoteSync -->|Diff & re-queue| QueueManager
QueueManager -->|1-10 async workers| API[Immich API Client - shared]
QueueManager -->|Update directly| SharedState
QueueManager -->|Pause policy checks| RuntimeEnv
API -->|LAN / WAN failover| Immich[(Immich Server)]
end
subgraph Library UI [GTK4 / Libadwaita]
Window[Library / Settings Window] -->|500ms timer| SharedState
Window -->|Diagnostics export| Diagnostics[diagnostics.rs]
Window -->|Fetch / Bidirectional Sync| API
Window -->|Server thumbnails + ThumbHash placeholder| ThumbCache[Thumbnail Cache - LRU + disk]
Window --> LocalSource[Local Source Enumeration]
Window --> LocalExif[Local EXIF Parser]
Window --> Lightbox[Lightbox + Multi-format Decoders]
Lightbox -->|libraw, libheif-rs, jxl-oxide, resvg, psd, turbojpeg| ThumbCache
Lightbox -->|Video handoff: spawn_video_handoff| ExternalPlayer[System Default Player]
LocalSource -->|Synthetic asset rows| Window
LocalExif -->|Cached EXIF| Window
end
Main -->|present / set_visible| Window
subgraph Storage
Config["config.json (~/.config/mimick/)"]
Keyring[Keyring - oo7 via Secure Portal / D-Bus]
RetryFile["retries.json (~/.cache/mimick/) - written on shutdown only"]
StateFile["status.json (~/.cache/mimick/) - written on shutdown only"]
ThumbStore["thumbnails/ (~/.cache/mimick/thumbnails/)"]
RawCache["raw_decode/ (~/.cache/mimick/raw_decode/)"]
ExifCache["exif/ (~/.cache/mimick/exif/)"]
VideoCache["video/ (~/.cache/mimick/video/) - external player handoff"]
SyncIndex["synced_index.json (~/.local/share/mimick/)"]
end
Window -->|Read/Write| Config
Window -->|Read/Write| Keyring
Main -->|Read on startup, Write on exit| StateFile
QueueManager -->|Write on exit| RetryFile
Main -->|Read/Write| SyncIndex
ThumbCache -->|LRU / Write| ThumbStore
Lightbox -->|Demosaic cache write| RawCache
LocalExif -->|Write| ExifCache
Lightbox -->|Remote video download| VideoCache
Diagnostics -->|Copy selected files| Storage
Initializes the GTK4 adw::Application with ID dev.nicx.mimick. Only the primary instance (determined via D-Bus single-instance enforcement) spawns daemon services. Secondary processes forward their command line to the primary via GTK's single-instance mechanism.
-
connect_command_lineopens the library window when--settingsor--libraryis passed or whencmdline.is_remote()is true (user clicks the app icon while the daemon is already running) - Shared
Arc<Mutex<AppState>>is created before all closures and threaded into bothconnect_startup(workers) andconnect_command_line(library window) - Shared
AppContextis stored in aOnceLockand reused by UI windows and shutdown paths (single API client, queue manager, monitor handle)
Uses the notify crate for inotify-based filesystem events. Inside Flatpak, falls back to a polling watcher so portal-backed directories continue to sync reliably.
- Filters to media extensions only
- Applies per-folder rules before queueing files
- Ignores temporary files until the final media filename exists
- 2-second debounce per path
- Per-file
wait_for_file_completion(async, Tokio task -- no OS thread per file) - SHA-1 checksum via 64KB chunked
spawn_blocking(no load into RAM) - Supports live watch-path replacement via
MonitorHandlewithout restarting the daemon
Thread-safe upload orchestrator using a single Arc<Mutex<AppState>> for all counters and status.
-
Configurable async workers (1-10, default 3) sharing one
mpsc::ReceiverviaArc<tokio::sync::Mutex> - Workers update
AppStatedirectly in memory -- no disk writes during uploads -
In-memory retry list (
Arc<std::sync::Mutex<Vec<FileTask>>>): retries on next successful upload (network recovery), flushed to disk only on graceful shutdown viaQueueManager::flush_retries() - Reads persisted retries from disk on startup (crash recovery); re-queues after 5s delay
- Supports manual
Pause / Resume,Sync Now, queue inspection, per-item retry, retry-all, and failed-queue clearing - Records recent queue events and pause reasons in shared state for UI visibility and diagnostics
- The
QueueManagerlives inAppContext; shutdown callsflush_retries()from the shared handle afterapp.run()returns - Environment-aware pause policy: metered network, battery power, and quiet-hours window deferral
- LAN-first, WAN fallback connectivity check
- Active URL cached in
Mutex<Option<String>>; cleared on network error to force re-check - Files streamed with
reqwest::multipart::Part::stream_with_length-- zero RAM buffering - Full 40-char SHA-1 hex used as
device_asset_idfor reliable Immich server-side deduplication - Connection pool: max 1 idle connection per host, 30s idle timeout
- Single shared instance via
AppContext-- no new reqwest pool per window open - Structured error diagnostics with actionable guidance for common failure modes
- Extended endpoints for library integration (Explore, Albums, Search, Smart Search, OCR)
- The main user interface is a unified
adw::PreferencesWindowthat doubles as the Library View and the Settings panel. - Built on demand from
AppContext; close destroys the window (unless background sync is disabled, in which case the app quits) - Includes distinct library pages: Photos, Explore, Albums, and Search.
- Supports manual uploads via the
upload_pickermodule, directly enqueueing multi-file selections. -
Arc<Mutex<AppState>>accessed fromAppContext; the 500msglib::timeout_add_localtimer reads it without any disk I/O to drive the footer Sync Status indicator. - Test Connection button uses the shared client -- no new reqwest pool per click
- Saving applies updated API settings, queue policy, worker limit, and watched folders to the running process without a restart
Provides fast, responsive image previews for the Library View.
- Uses
ThumbHashand Base64 encoded payload storage for extremely small footprints. - Two-tier caching mechanism:
-
Memory Cache:
lru::LruCachefor instantaneous rendering while browsing. -
Disk Cache: Persisted to
~/.cache/mimick/thumbnails/to survive application restarts.
-
Memory Cache:
Supports bidirectional synchronization directly from the Albums page.
- Local Source: Enumerates files in configured watch folders and generates synthetic standard assets identical in shape to remote assets.
- Album Sync: Compares SHA-1 hashes of local source items against remote album assets to identify missing items in either direction (uploads vs downloads).
- Collision avoidance using numeric suffixes (e.g.,
file (1).jpg) when downloading.
- Uses
ksni(D-Bus StatusNotifierItem) - Clicking tray actions sends signals back to the GTK main loop -- no child process spawned
- A
glib::timeout_add_local(250ms)in the GTK main loop polls anArc<Mutex<bool>>flag set by the Tokio watch-forward task; on trigger, callsopen_settings_if_neededin-process
Best-effort helpers used by queue policy:
-
nmcliparsing to detect metered or guessed-metered connections -
/sys/class/power_supplyinspection to detect battery-powered operation
These checks are advisory and only affect uploads when the corresponding behavior switches are enabled.
Walks all configured watch folders on launch and queues media files that are not yet recorded in the sync index.
- Respects the user's configured startup catch-up mode (
Full,RecentOnly,NewFilesOnly) - Applies the same per-folder rules and media-extension filter used by the live monitor
- Detects album-target changes and queues reassociation-only tasks for unchanged files that have moved albums
- Also used by the manual
Sync Nowaction from the tray and Library View footer
Maintains a persistent map of previously synced files at ~/.local/share/mimick/synced_index.json (moved from the cache directory to survive user cache clears).
- Keyed by file path, stores SHA-1 checksum, album name, and album ID
- Used during startup scans to skip unchanged files and avoid redundant uploads
- Detects when a file's content has changed (checksum mismatch) and re-queues it
- Detects album-target changes and triggers reassociation without re-uploading the file data
Provides desktop notification helpers using gio::Notification and the XDG notification portal.
- Global enable/disable toggle controlled by the user-facing "Enable Notifications" switch
- Batch sync-complete summary notification (fired once when the queue drains, not per file)
- Connectivity-lost notification (fired once per session after consecutive upload failures)
- All dispatches route through
glib::idle_add_onceto stay on the GTK main thread
Exports a redacted support bundle for troubleshooting.
- Writes
summary.txt,privacy-note.txt,config.redacted.json,status.redacted.json,retries.redacted.json, andsynced_index.redacted.json - All local paths are redacted to filename-only hints
- API keys, raw logs, server URLs, and full local paths are intentionally omitted
Handles autostart integration for both native and Flatpak contexts.
- Inside Flatpak: requests background permission via the
ashpdBackground portal - Outside Flatpak: writes a standard autostart desktop entry to
~/.config/autostart/dev.nicx.mimick.desktop - Disabling autostart removes the entry (or revokes the portal request)
Utility module for displaying user-friendly labels for watch paths, especially portal-backed Flatpak folders.
- Detects document-portal paths (
/run/user/.../doc/...) and shows the folder name instead - Provides subtitle hints for portal-selected folders in the UI
Periodic background task that re-runs the per-folder album↔folder diff on a fixed interval (5 min) so changes made directly in Immich (asset additions, deletions, album moves) propagate to local folders without requiring an app restart.
- Skips silently when background sync is disabled, when the queue is paused, or when the API connectivity check fails
- Delegates the actual diff to
startup_scan::reconcile_entry, so the catch-up code path is shared between launch and steady-state reconciliation - Runs alongside the live file monitor; the monitor handles fresh local edits while this loop handles remote-side and missed events
Full-screen viewer with its own multi-format decoder pipeline, independent of the grid thumbnail cache.
- Decoder routing by extension (see
texture_decoder_for_path): RAW via libraw + imagepipe fallback, HEIF/AVIF via libheif-rs, JXL via jxl-oxide, SVG via resvg, PSD via the pure-Rustpsdcrate, JPEG via turbojpeg, then pixbuf, then theimagecrate as final fallback - RAW fast path extracts the embedded camera JPEG preview, picking the largest SOI-bounded payload in the file (resolves tiny previews on Samsung DNG and similar); the full demosaic path is gated behind the "Full RAW Decoding" setting and writes to
~/.cache/mimick/raw_decode/ - Videos take a poster path: the still thumbnail is shown with a clickable play badge; clicking hands off to the same external-player flow used by grid clicks (
open_local_with_default_appfor local files,spawn_video_handofffor remote files via~/.cache/mimick/video/) - EXIF details fetched via the API for remote assets or via
local_exiffor local files (skipped entirely for videos)
-
AppState: in-memory struct shared by workers and UI viaArc<Mutex<AppState>>; includes pause state, pause reason, last completed file, diagnostics export count, and recent queue events -
StateManager(src/state_manager.rs): reads saved state on startup for crash recovery; writes on graceful shutdown only -
Retry queue: in-memory during session; disk path
~/.cache/mimick/retries.jsonwritten only on exit -
Sync index: persisted at
~/.local/share/mimick/synced_index.jsonto skip unchanged files across restarts and to detect album-target changes -
Thumbnail cache:
~/.cache/mimick/thumbnails/for fast preview rendering -
RAW Decode cache:
~/.cache/mimick/raw_decode/for persisting full sensor demosaics -
Local EXIF cache:
~/.cache/mimick/exif/for persisting local metadata reads -
Notifications: natively handled via
gio::Notificationand the XDG notification portal; ensures safe delivery withoutnotify-sendsub-processes when sandboxed -
Keyring: API keys are securely persisted through the
oo7crate -- natively talking D-Bus Secret Service, or reading an encrypted sandbox file when running across the Flatpak boundary