feat(hardening): production logging, single-instance, CSP, and scoped asset protocol
Phase 4 of the 0.1.0 release prep: - tauri-plugin-log: rotating file log (5 MB, Info) in the app log dir plus stdout; all 54 backend println!/eprintln! sites migrated to log macros so release builds finally produce diagnostics - tauri-plugin-single-instance (registered first): second launches focus the existing window instead of racing the same SQLite DB with a second worker fleet; tauri-plugin-window-state persists window geometry - real CSP replacing null (scripts self-only, media/images via the asset protocol, IPC endpoints whitelisted, object-src none) with a devCsp variant for Vite HMR - asset protocol scope narrowed from '**' to thumbnails statically plus per-folder runtime grants (setup, add_folder, update_folder_path) Panic audit (plan item) concluded with no changes: worker loops already catch and log all Result errors; remaining unwraps are startup fail-fast, poisoned-lock convention, infallible-by-construction, or test code.
This commit is contained in:
@@ -470,7 +470,7 @@ impl FlorenceCaptioner {
|
||||
acceleration,
|
||||
false,
|
||||
)?;
|
||||
println!(
|
||||
log::info!(
|
||||
"Florence sessions loaded in {:?} with {:?} acceleration (total init {:?})",
|
||||
sessions_started_at.elapsed(),
|
||||
acceleration,
|
||||
@@ -490,9 +490,9 @@ impl FlorenceCaptioner {
|
||||
|
||||
pub fn generate(&mut self, image_path: &Path) -> Result<String> {
|
||||
let started_at = Instant::now();
|
||||
println!("Florence caption started: {}", image_path.display());
|
||||
log::info!("Florence caption started: {}", image_path.display());
|
||||
let image_features = run_vision_encoder(&mut self.vision_session, image_path)?;
|
||||
println!("Florence vision encoder done in {:?}", started_at.elapsed());
|
||||
log::info!("Florence vision encoder done in {:?}", started_at.elapsed());
|
||||
let prompt_ids = self
|
||||
.tokenizer
|
||||
.encode(self.caption_detail.prompt(), false)
|
||||
@@ -502,7 +502,7 @@ impl FlorenceCaptioner {
|
||||
.map(|id| i64::from(*id))
|
||||
.collect::<Vec<_>>();
|
||||
let prompt_embeds = run_token_embedder(&mut self.embed_session, &prompt_ids)?;
|
||||
println!(
|
||||
log::info!(
|
||||
"Florence token embeddings done in {:?}",
|
||||
started_at.elapsed()
|
||||
);
|
||||
@@ -513,7 +513,7 @@ impl FlorenceCaptioner {
|
||||
&encoder_embeds,
|
||||
&encoder_attention_mask,
|
||||
)?;
|
||||
println!("Florence encoder done in {:?}", started_at.elapsed());
|
||||
log::info!("Florence encoder done in {:?}", started_at.elapsed());
|
||||
|
||||
let generated_ids = run_decoder(
|
||||
&mut self.decoder_prefill_session,
|
||||
@@ -523,7 +523,7 @@ impl FlorenceCaptioner {
|
||||
&encoder_attention_mask,
|
||||
self.caption_detail.max_new_tokens(),
|
||||
)?;
|
||||
println!("Florence decoder done in {:?}", started_at.elapsed());
|
||||
log::info!("Florence decoder done in {:?}", started_at.elapsed());
|
||||
|
||||
let generated_u32 = generated_ids
|
||||
.into_iter()
|
||||
@@ -784,7 +784,7 @@ fn run_decoder(
|
||||
"inputs_embeds" => prefill_inputs_embeds_tensor
|
||||
})
|
||||
.map_err(|error| anyhow::anyhow!("{error}"))?;
|
||||
println!(
|
||||
log::info!(
|
||||
"Florence decoder prefill done in {:?}",
|
||||
prefill_started_at.elapsed()
|
||||
);
|
||||
@@ -848,7 +848,7 @@ fn run_decoder(
|
||||
decoder_kv = collect_present_key_values(&outputs, DECODER_LAYERS)?;
|
||||
}
|
||||
|
||||
println!("Florence decoder produced {} token(s)", generated.len());
|
||||
log::info!("Florence decoder produced {} token(s)", generated.len());
|
||||
Ok(generated)
|
||||
}
|
||||
|
||||
@@ -886,7 +886,7 @@ fn create_session(
|
||||
CaptionAcceleration::Auto | CaptionAcceleration::Directml => {
|
||||
// `allow_directml` is false for the 4 text/decoder sessions —
|
||||
// they intentionally run on CPU regardless of the setting.
|
||||
println!(
|
||||
log::info!(
|
||||
"Florence: using CPU for {} (DirectML disabled for this session type)",
|
||||
path.display()
|
||||
);
|
||||
|
||||
@@ -227,6 +227,14 @@ pub async fn add_folder(
|
||||
return Err("Path is not a valid directory".into());
|
||||
}
|
||||
|
||||
// Let the webview load media from this folder via the asset protocol.
|
||||
if let Err(error) = app
|
||||
.asset_protocol_scope()
|
||||
.allow_directory(&folder_path, true)
|
||||
{
|
||||
log::error!("Failed to allow asset scope for {}: {}", path, error);
|
||||
}
|
||||
|
||||
let name = folder_path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
@@ -397,6 +405,15 @@ pub async fn update_folder_path(
|
||||
if !new_path_buf.is_dir() {
|
||||
return Err(format!("Path is not a valid directory: {}", new_path));
|
||||
}
|
||||
|
||||
// Let the webview load media from the relocated folder via the asset protocol.
|
||||
if let Err(error) = app
|
||||
.asset_protocol_scope()
|
||||
.allow_directory(&new_path_buf, true)
|
||||
{
|
||||
log::error!("Failed to allow asset scope for {}: {}", new_path, error);
|
||||
}
|
||||
|
||||
let new_name = new_path_buf
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
|
||||
@@ -20,7 +20,7 @@ fn with_text_embedder<T>(f: impl FnOnce(&ClipImageEmbedder) -> Result<T>) -> Res
|
||||
.lock()
|
||||
.map_err(|_| anyhow::anyhow!("Text embedder lock poisoned"))?;
|
||||
if guard.is_none() {
|
||||
println!("Initializing CLIP text embedder...");
|
||||
log::info!("Initializing CLIP text embedder...");
|
||||
*guard = Some(ClipImageEmbedder::new()?);
|
||||
}
|
||||
f(guard.as_ref().unwrap())
|
||||
@@ -35,13 +35,13 @@ pub struct ClipImageEmbedder {
|
||||
|
||||
impl ClipImageEmbedder {
|
||||
pub fn new() -> Result<Self> {
|
||||
println!("Initializing CLIP image embedder...");
|
||||
log::info!("Initializing CLIP image embedder...");
|
||||
let api = Api::new()?;
|
||||
let repo = api.repo(Repo::new(
|
||||
"laion/CLIP-ViT-B-32-laion2B-s34B-b79K".to_string(),
|
||||
RepoType::Model,
|
||||
));
|
||||
println!("Resolving CLIP model weights from Hugging Face cache...");
|
||||
log::info!("Resolving CLIP model weights from Hugging Face cache...");
|
||||
let model_path = repo.get("model.safetensors")?;
|
||||
let tokenizer_repo = api.repo(Repo::new(
|
||||
"openai/clip-vit-base-patch32".to_string(),
|
||||
@@ -60,7 +60,7 @@ impl ClipImageEmbedder {
|
||||
};
|
||||
let model = ClipModel::new(vb, &config)?;
|
||||
let tokenizer = Tokenizer::from_file(tokenizer_path).map_err(anyhow::Error::msg)?;
|
||||
println!("CLIP image embedder ready.");
|
||||
log::info!("CLIP image embedder ready.");
|
||||
|
||||
Ok(Self {
|
||||
model,
|
||||
@@ -177,14 +177,14 @@ impl ClipImageEmbedder {
|
||||
fn resolve_device() -> Result<Device> {
|
||||
match Device::cuda_if_available(0) {
|
||||
Ok(device) if device.is_cuda() => {
|
||||
println!("CLIP embedder: using CUDA GPU (device 0).");
|
||||
log::info!("CLIP embedder: using CUDA GPU (device 0).");
|
||||
return Ok(device);
|
||||
}
|
||||
Ok(_) => {
|
||||
println!("CLIP embedder: no compatible CUDA GPU found — using CPU.");
|
||||
log::info!("CLIP embedder: no compatible CUDA GPU found — using CPU.");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("CLIP embedder: CUDA init failed ({e}) — using CPU.");
|
||||
log::info!("CLIP embedder: CUDA init failed ({e}) — using CPU.");
|
||||
}
|
||||
}
|
||||
Ok(Device::Cpu)
|
||||
|
||||
+33
-29
@@ -200,7 +200,7 @@ pub fn index_folder(app: AppHandle, pool: DbPool, folder_id: i64, folder_path: P
|
||||
// not be silently destroyed.
|
||||
if !folder_path.is_dir() {
|
||||
let error_msg = format!("Folder not found: {}", folder_path.display());
|
||||
eprintln!("Indexing error for folder {}: {}", folder_id, error_msg);
|
||||
log::error!("Indexing error for folder {}: {}", folder_id, error_msg);
|
||||
if let Ok(conn) = pool.get() {
|
||||
let _ = db::set_folder_scan_error(&conn, folder_id, &error_msg);
|
||||
}
|
||||
@@ -221,7 +221,7 @@ pub fn index_folder(app: AppHandle, pool: DbPool, folder_id: i64, folder_path: P
|
||||
let storage_profile = detect_storage_profile(&folder_path);
|
||||
set_folder_storage_profile(folder_id, RuntimeAdaptiveProfile::new(storage_profile));
|
||||
if let Err(error) = do_index(app.clone(), &pool, folder_id, folder_path) {
|
||||
eprintln!("Indexing error for folder {}: {}", folder_id, error);
|
||||
log::error!("Indexing error for folder {}: {}", folder_id, error);
|
||||
if let Ok(conn) = pool.get() {
|
||||
let _ = db::set_folder_scan_error(&conn, folder_id, &error.to_string());
|
||||
}
|
||||
@@ -254,7 +254,7 @@ pub fn start_thumbnail_worker(
|
||||
Ok(true) => {}
|
||||
Ok(false) => std::thread::sleep(std::time::Duration::from_millis(250)),
|
||||
Err(error) => {
|
||||
eprintln!("Thumbnail worker error: {}", error);
|
||||
log::error!("Thumbnail worker error: {}", error);
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
}
|
||||
}
|
||||
@@ -269,7 +269,7 @@ pub fn start_metadata_worker(app: AppHandle, pool: DbPool, media_tools: MediaToo
|
||||
Ok(true) => {}
|
||||
Ok(false) => std::thread::sleep(std::time::Duration::from_millis(250)),
|
||||
Err(error) => {
|
||||
eprintln!("Metadata worker error: {}", error);
|
||||
log::error!("Metadata worker error: {}", error);
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
}
|
||||
}
|
||||
@@ -279,7 +279,7 @@ pub fn start_metadata_worker(app: AppHandle, pool: DbPool, media_tools: MediaToo
|
||||
pub fn start_embedding_worker(app: AppHandle, pool: DbPool) {
|
||||
std::thread::spawn(move || {
|
||||
let mut embedder: Option<ClipImageEmbedder> = None;
|
||||
println!("Embedding worker started.");
|
||||
log::info!("Embedding worker started.");
|
||||
loop {
|
||||
// Only back off when the queue is empty (or errored); while jobs
|
||||
// are pending, claim the next batch immediately.
|
||||
@@ -287,7 +287,7 @@ pub fn start_embedding_worker(app: AppHandle, pool: DbPool) {
|
||||
Ok(true) => {}
|
||||
Ok(false) => std::thread::sleep(std::time::Duration::from_millis(500)),
|
||||
Err(error) => {
|
||||
eprintln!("Embedding worker error: {}", error);
|
||||
log::error!("Embedding worker error: {}", error);
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
}
|
||||
@@ -298,16 +298,16 @@ pub fn start_embedding_worker(app: AppHandle, pool: DbPool) {
|
||||
pub fn start_caption_worker(app: AppHandle, pool: DbPool, app_data_dir: PathBuf) {
|
||||
std::thread::spawn(move || {
|
||||
let mut captioner: Option<FlorenceCaptioner> = None;
|
||||
println!("Caption worker started.");
|
||||
log::info!("Caption worker started.");
|
||||
loop {
|
||||
// If the acceleration setting changed, drop the cached session so
|
||||
// the next batch picks it up with the new execution provider.
|
||||
if captioner::CAPTION_SESSION_DIRTY.swap(false, std::sync::atomic::Ordering::Relaxed) {
|
||||
println!("Caption worker: acceleration setting changed — resetting session.");
|
||||
log::info!("Caption worker: acceleration setting changed — resetting session.");
|
||||
captioner = None;
|
||||
}
|
||||
if let Err(error) = process_caption_batch(&app, &pool, &app_data_dir, &mut captioner) {
|
||||
eprintln!("Caption worker error: {}", error);
|
||||
log::error!("Caption worker error: {}", error);
|
||||
captioner = None;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(750));
|
||||
@@ -318,12 +318,12 @@ pub fn start_caption_worker(app: AppHandle, pool: DbPool, app_data_dir: PathBuf)
|
||||
pub fn start_tagging_worker(app: AppHandle, pool: DbPool, app_data_dir: PathBuf) {
|
||||
std::thread::spawn(move || {
|
||||
let mut tagger_instance: Option<WdTagger> = None;
|
||||
println!("Tagging worker started.");
|
||||
log::info!("Tagging worker started.");
|
||||
loop {
|
||||
// If the acceleration setting changed, drop the cached session so
|
||||
// the next batch picks it up with the new execution provider.
|
||||
if tagger::TAGGER_SESSION_DIRTY.swap(false, std::sync::atomic::Ordering::Relaxed) {
|
||||
println!("Tagging worker: acceleration setting changed — resetting session.");
|
||||
log::info!("Tagging worker: acceleration setting changed — resetting session.");
|
||||
tagger_instance = None;
|
||||
}
|
||||
// Only back off when the queue is empty (or errored); while jobs
|
||||
@@ -332,7 +332,7 @@ pub fn start_tagging_worker(app: AppHandle, pool: DbPool, app_data_dir: PathBuf)
|
||||
Ok(true) => {}
|
||||
Ok(false) => std::thread::sleep(std::time::Duration::from_millis(750)),
|
||||
Err(error) => {
|
||||
eprintln!("Tagging worker error: {}", error);
|
||||
log::error!("Tagging worker error: {}", error);
|
||||
tagger_instance = None;
|
||||
std::thread::sleep(std::time::Duration::from_millis(750));
|
||||
}
|
||||
@@ -369,7 +369,7 @@ fn do_index(app: AppHandle, pool: &DbPool, folder_id: i64, folder_path: PathBuf)
|
||||
if let Some(path) = err.path() {
|
||||
unreadable_prefixes.push(path.to_string_lossy().into_owned());
|
||||
}
|
||||
eprintln!(
|
||||
log::error!(
|
||||
"WalkDir error while scanning folder {}: {}",
|
||||
folder_path.display(),
|
||||
err
|
||||
@@ -649,7 +649,7 @@ fn process_thumbnail_batch(
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
println!("Thumbnail batch claimed: {} items", jobs.len());
|
||||
log::info!("Thumbnail batch claimed: {} items", jobs.len());
|
||||
|
||||
let (image_jobs, video_jobs): (Vec<_>, Vec<_>) =
|
||||
jobs.into_iter().partition(|job| job.media_kind == "image");
|
||||
@@ -846,7 +846,7 @@ fn process_embedding_batch(
|
||||
*embedder = Some(ClipImageEmbedder::new()?);
|
||||
}
|
||||
|
||||
println!("Embedding batch claimed: {} items", jobs.len());
|
||||
log::info!("Embedding batch claimed: {} items", jobs.len());
|
||||
let folder_ids = jobs.iter().map(|job| job.folder_id).collect::<HashSet<_>>();
|
||||
emit_folder_job_progress(
|
||||
app,
|
||||
@@ -897,7 +897,7 @@ fn process_embedding_batch(
|
||||
}
|
||||
}
|
||||
Err(batch_error) => {
|
||||
eprintln!(
|
||||
log::error!(
|
||||
"Embedding batch fallback to per-image mode: {}",
|
||||
batch_error
|
||||
);
|
||||
@@ -946,7 +946,7 @@ fn process_embedding_batch(
|
||||
})?;
|
||||
|
||||
if !updated_images.is_empty() {
|
||||
println!("Embedding batch completed: {} items", updated_images.len());
|
||||
log::info!("Embedding batch completed: {} items", updated_images.len());
|
||||
let folder_ids = updated_images
|
||||
.iter()
|
||||
.map(|image| image.folder_id)
|
||||
@@ -962,9 +962,13 @@ fn process_embedding_batch(
|
||||
|
||||
let write_elapsed = write_started_at.elapsed();
|
||||
let batch_elapsed = batch_started_at.elapsed();
|
||||
println!(
|
||||
log::info!(
|
||||
"Embedding batch timing: claimed {} in {:?}, infer {:?}, write {:?}, total {:?}",
|
||||
EMBEDDING_BATCH_SIZE, claim_elapsed, infer_elapsed, write_elapsed, batch_elapsed
|
||||
EMBEDDING_BATCH_SIZE,
|
||||
claim_elapsed,
|
||||
infer_elapsed,
|
||||
write_elapsed,
|
||||
batch_elapsed
|
||||
);
|
||||
|
||||
Ok(true)
|
||||
@@ -1397,7 +1401,7 @@ impl WatcherHandle {
|
||||
let mut w = self.inner.watcher.lock().unwrap();
|
||||
if path.is_dir() {
|
||||
if let Err(e) = w.watch(&path, RecursiveMode::Recursive) {
|
||||
eprintln!("Watcher: failed to watch {:?}: {}", path, e);
|
||||
log::error!("Watcher: failed to watch {:?}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1422,7 +1426,7 @@ impl WatcherHandle {
|
||||
let _ = w.unwatch(old_path);
|
||||
if new_path.is_dir() {
|
||||
if let Err(e) = w.watch(&new_path, RecursiveMode::Recursive) {
|
||||
eprintln!("Watcher: failed to watch {:?}: {}", new_path, e);
|
||||
log::error!("Watcher: failed to watch {:?}: {}", new_path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1467,7 +1471,7 @@ pub fn start_watcher(app: AppHandle, pool: DbPool, thumb_dir: PathBuf) -> Watche
|
||||
for path in map.keys() {
|
||||
if path.is_dir() {
|
||||
if let Err(e) = w.watch(path, RecursiveMode::Recursive) {
|
||||
eprintln!("Watcher: failed to watch {:?}: {}", path, e);
|
||||
log::error!("Watcher: failed to watch {:?}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1593,7 +1597,7 @@ fn process_watcher_path(
|
||||
let conn = match pool.get() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("Watcher: DB pool error: {}", e);
|
||||
log::error!("Watcher: DB pool error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -1628,7 +1632,7 @@ fn process_watcher_path(
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(e) => eprintln!("Watcher: commit error for {:?}: {}", path, e),
|
||||
Err(e) => log::error!("Watcher: commit error for {:?}: {}", path, e),
|
||||
}
|
||||
} else {
|
||||
// File removed from disk — clean up DB row and thumbnail.
|
||||
@@ -1645,7 +1649,7 @@ fn process_watcher_path(
|
||||
}
|
||||
}
|
||||
Ok(None) => {} // never indexed or already removed
|
||||
Err(e) => eprintln!("Watcher: lookup error for {:?}: {}", path, e),
|
||||
Err(e) => log::error!("Watcher: lookup error for {:?}: {}", path, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1675,7 +1679,7 @@ fn process_watcher_rename(
|
||||
let conn = match pool.get() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("Watcher rename: DB pool error: {}", e);
|
||||
log::error!("Watcher rename: DB pool error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -1692,7 +1696,7 @@ fn process_watcher_rename(
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Watcher rename: DB lookup error: {}", e);
|
||||
log::error!("Watcher rename: DB lookup error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -1717,7 +1721,7 @@ fn process_watcher_rename(
|
||||
new_filename,
|
||||
effective_thumb,
|
||||
) {
|
||||
eprintln!("Watcher rename: DB update error: {}", e);
|
||||
log::error!("Watcher rename: DB update error: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1730,6 +1734,6 @@ fn process_watcher_rename(
|
||||
images: vec![record],
|
||||
},
|
||||
),
|
||||
Err(e) => eprintln!("Watcher rename: post-update fetch error: {}", e),
|
||||
Err(e) => log::error!("Watcher rename: post-update fetch error: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
+37
-2
@@ -16,6 +16,28 @@ use tauri::Manager;
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
// Must be the first plugin: a second launch hands its args to the
|
||||
// running instance and exits before anything else initializes.
|
||||
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.unminimize();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}))
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::new()
|
||||
.targets([
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::Stdout),
|
||||
tauri_plugin_log::Target::new(tauri_plugin_log::TargetKind::LogDir {
|
||||
file_name: Some("phokus".into()),
|
||||
}),
|
||||
])
|
||||
.level(log::LevelFilter::Info)
|
||||
.max_file_size(5 * 1024 * 1024)
|
||||
.rotation_strategy(tauri_plugin_log::RotationStrategy::KeepOne)
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_window_state::Builder::new().build())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
@@ -43,12 +65,12 @@ pub fn run() {
|
||||
let backfilled =
|
||||
db::backfill_embedding_jobs(&conn).expect("Failed to backfill embedding jobs");
|
||||
if backfilled > 0 {
|
||||
println!("Backfilled {} embedding jobs.", backfilled);
|
||||
log::info!("Backfilled {} embedding jobs.", backfilled);
|
||||
}
|
||||
let (orphaned_vectors, missing_vectors) = db::repair_embedding_consistency(&conn)
|
||||
.expect("Failed to repair embedding consistency");
|
||||
if orphaned_vectors > 0 || missing_vectors > 0 {
|
||||
println!(
|
||||
log::info!(
|
||||
"Repaired embedding consistency: removed {} orphaned vectors, requeued {} missing vectors.",
|
||||
orphaned_vectors, missing_vectors
|
||||
);
|
||||
@@ -58,6 +80,19 @@ pub fn run() {
|
||||
let thumb_dir = app_dir.join("thumbnails");
|
||||
std::fs::create_dir_all(&thumb_dir).expect("Failed to create thumbnail dir");
|
||||
|
||||
// The asset protocol scope is no longer a blanket "**": thumbnails
|
||||
// are allowed statically in tauri.conf.json, and each indexed
|
||||
// folder is allowed here (and in add_folder/update_folder_path).
|
||||
{
|
||||
let scope = app.asset_protocol_scope();
|
||||
let conn = pool.get().expect("Failed to get connection for asset scope");
|
||||
for folder in db::get_folders(&conn).unwrap_or_default() {
|
||||
if let Err(error) = scope.allow_directory(&folder.path, true) {
|
||||
log::error!("Failed to allow asset scope for {}: {}", folder.path, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let thumbnail_worker_count = std::thread::available_parallelism()
|
||||
.map(|parallelism| StorageProfile::Balanced.thumbnail_workers(parallelism.get()))
|
||||
.unwrap_or(2);
|
||||
|
||||
@@ -43,22 +43,23 @@ impl MediaTools {
|
||||
}
|
||||
auto_download_with_progress(|event| match event {
|
||||
FfmpegDownloadProgressEvent::Starting => {
|
||||
println!("Downloading bundled FFmpeg...");
|
||||
log::info!("Downloading bundled FFmpeg...");
|
||||
}
|
||||
FfmpegDownloadProgressEvent::Downloading {
|
||||
total_bytes,
|
||||
downloaded_bytes,
|
||||
} => {
|
||||
println!(
|
||||
log::info!(
|
||||
"Downloading bundled FFmpeg: {}/{} bytes",
|
||||
downloaded_bytes, total_bytes
|
||||
downloaded_bytes,
|
||||
total_bytes
|
||||
);
|
||||
}
|
||||
FfmpegDownloadProgressEvent::UnpackingArchive => {
|
||||
println!("Unpacking bundled FFmpeg...");
|
||||
log::info!("Unpacking bundled FFmpeg...");
|
||||
}
|
||||
FfmpegDownloadProgressEvent::Done => {
|
||||
println!("Bundled FFmpeg ready.");
|
||||
log::info!("Bundled FFmpeg ready.");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -443,7 +443,7 @@ impl WdTagger {
|
||||
|
||||
let labels = load_labels(&labels_path)?;
|
||||
|
||||
println!(
|
||||
log::info!(
|
||||
"WD tagger loaded in {:?} ({} labels, input {}x{}, {:?} acceleration)",
|
||||
started_at.elapsed(),
|
||||
labels.len(),
|
||||
@@ -519,7 +519,7 @@ impl WdTagger {
|
||||
tags.sort_by(|a, b| b.confidence.total_cmp(&a.confidence));
|
||||
tags.truncate(max_tags);
|
||||
|
||||
println!(
|
||||
log::info!(
|
||||
"WD tagger: {} tags (threshold={}, rating={}) in {:?} for {}",
|
||||
tags.len(),
|
||||
self.threshold,
|
||||
@@ -559,13 +559,13 @@ fn create_tagger_session(path: &Path, acceleration: TaggerAcceleration) -> Resul
|
||||
|
||||
let mut builder = match acceleration {
|
||||
TaggerAcceleration::Cpu => {
|
||||
println!("WD tagger: using CPU execution provider");
|
||||
log::info!("WD tagger: using CPU execution provider");
|
||||
builder
|
||||
}
|
||||
TaggerAcceleration::Auto => builder
|
||||
.with_execution_providers([ep::DirectML::default().build().fail_silently()])
|
||||
.unwrap_or_else(|error| {
|
||||
println!("WD tagger: DirectML unavailable, falling back to CPU");
|
||||
log::info!("WD tagger: DirectML unavailable, falling back to CPU");
|
||||
error.recover()
|
||||
}),
|
||||
TaggerAcceleration::Directml => builder
|
||||
|
||||
Reference in New Issue
Block a user