feat: persist worker-pause state across restarts

Worker pause states can optionally survive app restarts. A new toggle in
Settings saves the current pause map to settings/worker_pauses.json; on
startup lib.rs restores it before workers are spawned. Backend: new
snapshot/replace helpers in indexer.rs, persist functions in commands.rs
(get/set_worker_pauses_persist). Frontend: workerPausesPersist store
field, load/setWorkerPausesPersist actions, toggle in SettingsModal.
This commit is contained in:
2026-06-29 13:21:53 +01:00
parent 949382f28c
commit d81624573d
7 changed files with 151 additions and 1 deletions
+67
View File
@@ -1947,6 +1947,7 @@ pub struct FolderWorkerStates {
#[tauri::command]
pub async fn set_worker_paused(
app: AppHandle,
db: State<'_, DbState>,
worker: String,
folder_id: i64,
@@ -1963,6 +1964,9 @@ pub async fn set_worker_paused(
db::requeue_processing_tagging_jobs_for_folder(&conn, folder_id)
.map_err(|e| e.to_string())?;
}
if let Err(error) = persist_worker_pauses_if_enabled(&app) {
log::warn!("Failed to persist worker pause state: {error}");
}
Ok(())
}
@@ -2482,6 +2486,8 @@ pub async fn bulk_remove_tag(
const TAGGING_QUEUE_SCOPE_FILE: &str = "settings/tagging_queue_scope.txt";
const TAGGING_QUEUE_FOLDER_IDS_FILE: &str = "settings/tagging_queue_folder_ids.txt";
const WORKER_PAUSES_PERSIST_FILE: &str = "settings/worker_pauses_persist.txt";
const WORKER_PAUSES_FILE: &str = "settings/worker_pauses.json";
#[derive(Deserialize)]
pub struct SetTaggingQueueScopeParams {
@@ -2553,6 +2559,67 @@ pub async fn set_tagging_queue_folder_ids(
Ok(())
}
fn worker_pause_persistence_enabled(app_dir: &Path) -> bool {
std::fs::read_to_string(app_dir.join(WORKER_PAUSES_PERSIST_FILE))
.map(|value| value.trim() == "true")
.unwrap_or(false)
}
fn write_worker_pause_snapshot(app_dir: &Path) -> Result<(), String> {
let path = app_dir.join(WORKER_PAUSES_FILE);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
}
let json = serde_json::to_string_pretty(&indexer::snapshot_worker_paused_states())
.map_err(|e| e.to_string())?;
std::fs::write(path, json).map_err(|e| e.to_string())
}
fn persist_worker_pauses_if_enabled(app: &AppHandle) -> Result<(), String> {
let app_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
if worker_pause_persistence_enabled(&app_dir) {
write_worker_pause_snapshot(&app_dir)?;
}
Ok(())
}
pub fn restore_persisted_worker_pauses(app_dir: &Path) {
if !worker_pause_persistence_enabled(app_dir) {
return;
}
let path = app_dir.join(WORKER_PAUSES_FILE);
let Ok(content) = std::fs::read_to_string(path) else {
return;
};
match serde_json::from_str::<indexer::PersistedPausedWorkerFolders>(&content) {
Ok(states) => indexer::replace_worker_paused_states(states),
Err(error) => log::warn!("Failed to restore persisted worker pauses: {error}"),
}
}
#[tauri::command]
pub async fn get_worker_pauses_persist(app: AppHandle) -> Result<bool, String> {
let app_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
Ok(worker_pause_persistence_enabled(&app_dir))
}
#[tauri::command]
pub async fn set_worker_pauses_persist(app: AppHandle, persist: bool) -> Result<(), String> {
let app_dir = app.path().app_data_dir().map_err(|e| e.to_string())?;
let path = app_dir.join(WORKER_PAUSES_PERSIST_FILE);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
}
std::fs::write(path, if persist { "true" } else { "false" }).map_err(|e| e.to_string())?;
if persist {
write_worker_pause_snapshot(&app_dir)?;
} else {
let _ = std::fs::remove_file(app_dir.join(WORKER_PAUSES_FILE));
}
Ok(())
}
// ---------------------------------------------------------------------------
// App data folder
// ---------------------------------------------------------------------------
+44 -1
View File
@@ -9,7 +9,7 @@ use crate::vector;
use anyhow::Result;
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use rayon::prelude::*;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, OnceLock};
@@ -41,6 +41,14 @@ struct PausedWorkerFolders {
tagging: HashSet<i64>,
}
#[derive(Default, Deserialize, Serialize)]
pub struct PersistedPausedWorkerFolders {
pub thumbnail: Vec<i64>,
pub metadata: Vec<i64>,
pub embedding: Vec<i64>,
pub tagging: Vec<i64>,
}
#[derive(Clone, Copy)]
pub struct FolderWorkerPausedState {
pub thumbnail: bool,
@@ -50,6 +58,41 @@ pub struct FolderWorkerPausedState {
pub tagging: bool,
}
pub fn replace_worker_paused_states(states: PersistedPausedWorkerFolders) {
if let Ok(mut paused_folders) = PAUSED_WORKER_FOLDERS
.get_or_init(|| Mutex::new(PausedWorkerFolders::default()))
.lock()
{
paused_folders.thumbnail = states.thumbnail.into_iter().collect();
paused_folders.metadata = states.metadata.into_iter().collect();
paused_folders.embedding = states.embedding.into_iter().collect();
paused_folders.caption = HashSet::new();
paused_folders.tagging = states.tagging.into_iter().collect();
}
}
pub fn snapshot_worker_paused_states() -> PersistedPausedWorkerFolders {
let Ok(paused_folders) = PAUSED_WORKER_FOLDERS
.get_or_init(|| Mutex::new(PausedWorkerFolders::default()))
.lock()
else {
return PersistedPausedWorkerFolders::default();
};
let sorted = |set: &HashSet<i64>| {
let mut ids = set.iter().copied().collect::<Vec<_>>();
ids.sort_unstable();
ids
};
PersistedPausedWorkerFolders {
thumbnail: sorted(&paused_folders.thumbnail),
metadata: sorted(&paused_folders.metadata),
embedding: sorted(&paused_folders.embedding),
tagging: sorted(&paused_folders.tagging),
}
}
pub fn set_worker_paused(worker: &str, folder_id: i64, paused: bool) {
if let Ok(mut paused_folders) = PAUSED_WORKER_FOLDERS
.get_or_init(|| Mutex::new(PausedWorkerFolders::default()))
+3
View File
@@ -119,6 +119,7 @@ pub fn run() {
let thumb_dir = app_dir.join("thumbnails");
std::fs::create_dir_all(&thumb_dir).expect("Failed to create thumbnail dir");
commands::restore_persisted_worker_pauses(&app_dir);
// The asset protocol scope is no longer a blanket "**": thumbnails
// are allowed statically in tauri.conf.json, and each indexed
@@ -195,6 +196,8 @@ pub fn run() {
commands::suggest_image_tags,
commands::set_worker_paused,
commands::get_worker_states,
commands::get_worker_pauses_persist,
commands::set_worker_pauses_persist,
commands::get_tag_cloud,
commands::get_explore_tags,
commands::get_related_tags,