Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src-tauri/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ pub struct AppState {
pub initial_file_path: Mutex<Option<String>>,
pub pending_edit_session: Mutex<Option<ExternalEditSession>>,
pub thumbnail_cancellation_token: Arc<AtomicBool>,
pub bulk_thumbnail_active: AtomicBool,
pub bulk_thumbnail_generation: AtomicUsize,
pub thumbnail_progress: Mutex<ThumbnailProgressTracker>,
pub preview_worker_tx: Mutex<Option<Sender<PreviewJob>>>,
pub analytics_worker_tx: Mutex<Option<Sender<AnalyticsJob>>>,
Expand Down
197 changes: 178 additions & 19 deletions src-tauri/src/file_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::hash::{Hash, Hasher};
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use std::thread;

use anyhow::Result;
Expand Down Expand Up @@ -253,7 +253,7 @@ impl fmt::Display for ReadFileError {

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ImageFile {
path: String,
pub path: String,
modified: u64,
is_edited: bool,
rating: u8,
Expand Down Expand Up @@ -1645,6 +1645,145 @@ pub fn start_thumbnail_workers(app_handle: tauri::AppHandle) {
}
}

#[tauri::command]
pub async fn generate_folder_thumbnails(
folder_path: String,
app_handle: tauri::AppHandle,
) -> Result<usize, String> {
let folder = PathBuf::from(&folder_path);
if !folder.is_dir() {
return Err(format!("Folder does not exist: {}", folder_path));
}

let image_paths: Vec<String> = list_images_recursive(folder_path.clone(), app_handle.clone())?
.into_iter()
.map(|image| image.path)
.collect();

let total = image_paths.len();
let settings = load_settings(app_handle.clone()).unwrap_or_default();
let worker_count = settings.thumbnail_worker_threads.unwrap_or(4).clamp(1, 16) as usize;
let cache_dir = get_thumb_cache_dir(&app_handle).map_err(|e| e.to_string())?;
let worker_pool = rayon::ThreadPoolBuilder::new()
.num_threads(worker_count)
.build()
.map_err(|e| format!("Could not start thumbnail workers: {}", e))?;

let state = app_handle.state::<crate::AppState>();
let generation = state
.bulk_thumbnail_generation
.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
+ 1;
state
.bulk_thumbnail_active
.store(true, std::sync::atomic::Ordering::SeqCst);

// The bulk job owns progress while active, so grid visibility cannot change its total.
state.thumbnail_manager.queue.lock().unwrap().clear();
{
let mut tracker = state.thumbnail_progress.lock().unwrap();
tracker.total = 0;
tracker.completed = 0;
}

let _ = app_handle.emit(
"thumbnail-progress",
serde_json::json!({ "current": 0, "total": total }),
);

if total == 0 {
state
.bulk_thumbnail_active
.store(false, std::sync::atomic::Ordering::SeqCst);
let _ = app_handle.emit("thumbnail-generation-complete", true);
return Ok(0);
}

let gpu_context = crate::gpu_processing::get_or_init_gpu_context(&state, &app_handle).ok();
let completed = Arc::new(Mutex::new(0usize));
let app_handle_clone = app_handle.clone();

tauri::async_runtime::spawn_blocking(move || {
worker_pool.install(|| {
image_paths.par_iter().for_each(|path| {
let current_state = app_handle_clone.state::<crate::AppState>();
if current_state
.bulk_thumbnail_generation
.load(std::sync::atomic::Ordering::SeqCst)
!= generation
{
return;
}

let result = generate_single_thumbnail_and_cache(
path,
&cache_dir,
gpu_context.as_ref(),
None,
false,
&app_handle_clone,
&settings,
);

if let Some((thumbnail_path, rating, is_edited)) = result {
emit_thumbnail_generated(
&app_handle_clone,
path,
&thumbnail_path,
rating,
is_edited,
);
}

if current_state
.bulk_thumbnail_generation
.load(std::sync::atomic::Ordering::SeqCst)
!= generation
{
return;
}

// Serialize increments and emissions so progress cannot arrive out of order.
let mut count = completed.lock().unwrap();
*count += 1;
let _ = app_handle_clone.emit(
"thumbnail-progress",
serde_json::json!({ "current": *count, "total": total }),
);
});
});

let current_state = app_handle_clone.state::<crate::AppState>();
if current_state
.bulk_thumbnail_generation
.load(std::sync::atomic::Ordering::SeqCst)
== generation
{
current_state
.bulk_thumbnail_active
.store(false, std::sync::atomic::Ordering::SeqCst);
let _ = app_handle_clone.emit("thumbnail-generation-complete", true);
}
})
.await
.map_err(|e| {
let current_state = app_handle.state::<crate::AppState>();
if current_state
.bulk_thumbnail_generation
.load(std::sync::atomic::Ordering::SeqCst)
== generation
{
current_state
.bulk_thumbnail_active
.store(false, std::sync::atomic::Ordering::SeqCst);
let _ = app_handle.emit("thumbnail-generation-complete", true);
}
e.to_string()
})?;

Ok(total)
}

#[tauri::command]
pub fn update_thumbnail_queue(
paths: Vec<String>,
Expand All @@ -1661,10 +1800,15 @@ pub fn update_thumbnail_queue(
tracker.completed = 0;
drop(tracker);

let _ = app_handle.emit(
"thumbnail-progress",
serde_json::json!({ "current": 0, "total": 0 }),
);
if !state
.bulk_thumbnail_active
.load(std::sync::atomic::Ordering::SeqCst)
{
let _ = app_handle.emit(
"thumbnail-progress",
serde_json::json!({ "current": 0, "total": 0 }),
);
}
state.thumbnail_manager.cvar.notify_all();
return Ok(());
}
Expand Down Expand Up @@ -1697,10 +1841,15 @@ pub fn update_thumbnail_queue(
let total = tracker.total;
drop(tracker);

let _ = app_handle.emit(
"thumbnail-progress",
serde_json::json!({ "current": current, "total": total }),
);
if !state
.bulk_thumbnail_active
.load(std::sync::atomic::Ordering::SeqCst)
{
let _ = app_handle.emit(
"thumbnail-progress",
serde_json::json!({ "current": current, "total": total }),
);
}

state.thumbnail_manager.cvar.notify_all();
Ok(())
Expand Down Expand Up @@ -1730,17 +1879,27 @@ pub fn increment_thumbnail_progress(state: &AppState, app_handle: &AppHandle) {
tracker.completed = 0;
drop(tracker);

let _ = app_handle.emit(
"thumbnail-progress",
serde_json::json!({ "current": 0, "total": 0 }),
);
let _ = app_handle.emit("thumbnail-generation-complete", true);
if !state
.bulk_thumbnail_active
.load(std::sync::atomic::Ordering::SeqCst)
{
let _ = app_handle.emit(
"thumbnail-progress",
serde_json::json!({ "current": 0, "total": 0 }),
);
let _ = app_handle.emit("thumbnail-generation-complete", true);
}
} else {
drop(tracker);
let _ = app_handle.emit(
"thumbnail-progress",
serde_json::json!({ "current": current, "total": total }),
);
if !state
.bulk_thumbnail_active
.load(std::sync::atomic::Ordering::SeqCst)
{
let _ = app_handle.emit(
"thumbnail-progress",
serde_json::json!({ "current": current, "total": total }),
);
}
}
}

Expand Down
9 changes: 9 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ fn cancel_thumbnail_generation(
state
.thumbnail_cancellation_token
.store(true, Ordering::SeqCst);
state
.bulk_thumbnail_generation
.fetch_add(1, Ordering::SeqCst);
state.bulk_thumbnail_active.store(false, Ordering::SeqCst);

state.thumbnail_manager.queue.lock().unwrap().clear();

let mut tracker = state.thumbnail_progress.lock().unwrap();
tracker.total = 0;
Expand Down Expand Up @@ -2280,6 +2286,8 @@ pub fn run() {
initial_file_path: Mutex::new(None),
pending_edit_session: Mutex::new(None),
thumbnail_cancellation_token: Arc::new(AtomicBool::new(false)),
bulk_thumbnail_active: AtomicBool::new(false),
bulk_thumbnail_generation: AtomicUsize::new(0),
thumbnail_progress: Mutex::new(ThumbnailProgressTracker { total: 0, completed: 0 }),
preview_worker_tx: Mutex::new(None),
analytics_worker_tx: Mutex::new(None),
Expand Down Expand Up @@ -2354,6 +2362,7 @@ pub fn run() {
file_management::get_folder_children,
file_management::get_pinned_folder_trees,
file_management::update_thumbnail_queue,
file_management::generate_folder_thumbnails,
file_management::create_folder,
file_management::delete_folder,
file_management::copy_files,
Expand Down
Loading