Bound native media preview capabilities and source range reads

This commit is contained in:
karl committed 2026-10-13 14:22:19 +02:00
1 parent 4b0b5ebfd9
commit 370db7e791
13 files changed
+834 -46

No files matched your search

+26 -32
View File
@@ -7,6 +7,7 @@ mod ai_commands;
mod appearance;
mod data_directory;
mod instance;
mod media_preview;
mod model_download;
mod semantic_commands;
mod settings;
@@ -44,6 +45,7 @@ struct AppState {
semantic: Arc<SemanticState>,
watching: Arc<watching::WatchService>,
index_options: searchi_core::IndexOptions,
previews: Arc<media_preview::PreviewRegistry>,
}
#[derive(Serialize)]
@@ -356,17 +358,20 @@ struct MediaPreview {
kind: &'static str,
}
// The asset protocol starts with an empty scope. Only a validated indexed
// media file is granted access; the webview receives no general filesystem API.
// Return a bounded opaque capability, never a lifetime asset-scope permission.
#[tauri::command]
async fn prepare_media_preview(
path: String,
app: tauri::AppHandle,
state: State<'_, AppState>,
) -> Result<MediaPreview, String> {
let database = state.database.clone();
let previews = state.previews.clone();
let admission = previews
.admit()
.ok_or("Media preview is busy; try again.")?;
tauri::async_runtime::spawn_blocking(move || {
let index = SearchIndex::open(database).map_err(|error| error.to_string())?;
let _admission = admission;
let index = SearchIndex::open(&database).map_err(|error| error.to_string())?;
let canonical = validated_result(&index, &path)?;
let kind = match searchi_media::classify(&canonical) {
Some(searchi_media::MediaKind::Image) => "image",
@@ -374,33 +379,8 @@ async fn prepare_media_preview(
Some(searchi_media::MediaKind::Video) => "video",
None => return Err("This file is not a supported media preview.".into()),
};
if kind == "image" {
let bytes = std::fs::metadata(&canonical)
.map_err(|error| error.to_string())?
.len();
if bytes > 64 * 1024 * 1024 {
return Err("Image exceeds the 64 MiB preview limit.".into());
}
let reader = image::ImageReader::open(&canonical)
.map_err(|error| error.to_string())?
.with_guessed_format()
.map_err(|error| error.to_string())?;
// Resource limits follow decoded format, not a renameable suffix.
let max_pixels = preview_pixel_limit(reader.format());
let (width, height) = reader
.into_dimensions()
.map_err(|error| format!("Image preview is unavailable: {error}"))?;
if width == 0 || height == 0 || u64::from(width) * u64::from(height) > max_pixels {
return Err("Image exceeds the supported preview dimensions.".into());
}
}
app.asset_protocol_scope()
.allow_file(&canonical)
.map_err(|error| error.to_string())?;
Ok(MediaPreview {
path: canonical.to_string_lossy().into_owned(),
kind,
})
let token = previews.grant(database, canonical, kind)?;
Ok(MediaPreview { path: token, kind })
})
.await
.map_err(|error| error.to_string())?
@@ -554,11 +534,24 @@ fn main() {
std::env::var_os("SEARCHI_DATA_DIR"),
)
.expect("searchi profile identity could not be resolved");
let previews = Arc::new(media_preview::PreviewRegistry::default());
let protocol_previews = previews.clone();
tauri::Builder::default()
// Detect duplicates before any model, database, watcher or other plugin starts.
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
instance::focus_search(app);
}))
.register_asynchronous_uri_scheme_protocol("searchi-media", move |_context, request, responder| {
let Some(admission) = protocol_previews.admit() else {
responder.respond(media_preview::error_response(tauri::http::StatusCode::SERVICE_UNAVAILABLE));
return;
};
let registry = protocol_previews.clone();
tauri::async_runtime::spawn_blocking(move || {
let _admission = admission;
responder.respond(registry.respond(request));
});
})
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().with_handler(|app, _, event| {
@@ -566,7 +559,7 @@ fn main() {
instance::focus_search(app);
}
}).build())
.setup(|app| {
.setup(move |app| {
let directory = data_directory::resolve(
app.path().app_data_dir()?,
std::env::var_os("SEARCHI_DATA_DIR"),
@@ -673,6 +666,7 @@ fn main() {
semantic,
watching,
index_options,
previews: previews.clone(),
});
Ok(())
})