feat: implement thumbnail generation for linked content identities and improve logging in responder

This commit is contained in:
Jamie Pine
2025-11-11 07:07:06 -08:00
parent 4dde6524af
commit 8d61dc1139
3 changed files with 183 additions and 14 deletions

View File

@@ -342,7 +342,7 @@ async fn handle_create(
debug!("✓ Generated content hash: {}", content_hash);
// Link the content identity
if let Err(e) = EntryProcessor::link_to_content_identity(
match EntryProcessor::link_to_content_identity(
ctx,
entry_id,
path,
@@ -351,9 +351,59 @@ async fn handle_create(
)
.await
{
warn!("Failed to link content identity for {}: {}", path.display(), e);
} else {
debug!("✓ Linked content identity for entry {}", entry_id);
Ok(link_result) => {
debug!("✓ Linked content identity for entry {}", entry_id);
// Generate thumbnails for the file
if let Some(library) = context.get_library(library_id).await {
debug!("→ Generating thumbnails for: {}", path.display());
// Get MIME type from content identity
let mime_type = if let Ok(Some(ci)) = crate::infra::db::entities::content_identity::Entity::find_by_id(link_result.content_identity.id)
.one(ctx.library_db())
.await
{
if let Some(mime_id) = ci.mime_type_id {
if let Ok(Some(mime_record)) = crate::infra::db::entities::mime_type::Entity::find_by_id(mime_id)
.one(ctx.library_db())
.await
{
Some(mime_record.mime_type)
} else {
None
}
} else {
None
}
} else {
None
};
if let Some(mime) = mime_type {
match crate::ops::media::thumbnail::generate_thumbnails_for_file(
&library,
&link_result.content_identity.uuid.expect("ContentIdentity should have UUID"),
path,
&mime,
)
.await
{
Ok(count) if count > 0 => {
debug!("✓ Generated {} thumbnails for: {}", count, path.display());
}
Ok(_) => {
debug!("No thumbnails generated for: {}", path.display());
}
Err(e) => {
debug!("Thumbnail generation failed for {}: {}", path.display(), e);
}
}
}
}
}
Err(e) => {
warn!("Failed to link content identity for {}: {}", path.display(), e);
}
}
} else {
debug!("✗ Failed to generate content hash for {}", path.display());

View File

@@ -20,3 +20,132 @@ pub use generator::{ImageGenerator, ThumbnailGenerator, ThumbnailInfo, VideoGene
pub use job::{ThumbnailJob, ThumbnailJobConfig};
pub use state::{ThumbnailEntry, ThumbnailPhase, ThumbnailState, ThumbnailStats};
pub use utils::ThumbnailUtils;
use crate::library::Library;
use crate::ops::sidecar::types::SidecarKind;
use std::path::Path;
use std::sync::Arc;
use uuid::Uuid;
/// Generate thumbnails for a single file (optimized for watcher/responder use)
///
/// This function generates the default thumbnail variants for a single file
/// and registers them as sidecars. It's designed to be called inline for
/// individual file creates/updates rather than dispatching a job.
///
/// # Arguments
/// * `library` - The library context
/// * `content_uuid` - The UUID of the content identity
/// * `source_path` - The filesystem path to the source file
/// * `mime_type` - The MIME type of the file
///
/// # Returns
/// Number of thumbnails successfully generated
pub async fn generate_thumbnails_for_file(
library: &Arc<Library>,
content_uuid: &Uuid,
source_path: &Path,
mime_type: &str,
) -> ThumbnailResult<usize> {
use tracing::{debug, warn};
// Check if thumbnail generation is supported for this file type
if !ThumbnailUtils::is_thumbnail_supported(mime_type) {
debug!("Thumbnail generation not supported for MIME type: {}", mime_type);
return Ok(0);
}
// Get sidecar manager
let sidecar_manager = library
.core_context()
.get_sidecar_manager()
.await
.ok_or_else(|| ThumbnailError::other("SidecarManager not available"))?;
// Create thumbnail generator for this MIME type
let generator = ThumbnailGenerator::for_mime_type(mime_type)?;
// Generate default variants (grid@1x, grid@2x, detail@1x)
let variants = ThumbnailVariants::defaults();
let mut generated_count = 0;
for variant_config in variants {
// Check if thumbnail already exists
if sidecar_manager
.exists(
&library.id(),
content_uuid,
&SidecarKind::Thumb,
&variant_config.variant,
&variant_config.format(),
)
.await
.unwrap_or(false)
{
debug!(
"Thumbnail already exists for {}: {}",
content_uuid,
variant_config.variant.as_str()
);
continue;
}
// Compute output path
let output_path = sidecar_manager
.compute_path(
&library.id(),
content_uuid,
&SidecarKind::Thumb,
&variant_config.variant,
&variant_config.format(),
)
.await
.map_err(|e| ThumbnailError::other(format!("Failed to compute path: {}", e)))?;
// Generate thumbnail
match generator
.generate(source_path, &output_path.absolute_path, variant_config.size, variant_config.quality)
.await
{
Ok(thumbnail_info) => {
// Record the sidecar in the database
if let Err(e) = sidecar_manager
.record_sidecar(
library,
content_uuid,
&SidecarKind::Thumb,
&variant_config.variant,
&variant_config.format(),
thumbnail_info.size_bytes as u64,
None,
)
.await
{
warn!(
"Failed to record sidecar for {}: {}",
variant_config.variant.as_str(),
e
);
} else {
debug!(
"✓ Generated thumbnail {}: {}x{}",
variant_config.variant.as_str(),
thumbnail_info.dimensions.0,
thumbnail_info.dimensions.1
);
generated_count += 1;
}
}
Err(e) => {
warn!(
"Failed to generate thumbnail {} for {}: {}",
variant_config.variant.as_str(),
source_path.display(),
e
);
}
}
}
Ok(generated_count)
}

View File

@@ -224,18 +224,8 @@ export function useNormalizedCache<I, O>({
// The filter checks parent path for Physical paths, which is correct for new files
const shouldAppend = resourceFilter(resource);
console.log('[useNormalizedCache] Batch - checking resource:', {
name: resource.name,
id: resource.id,
shouldAppend,
seenIds: Array.from(seenIds)
});
if (shouldAppend) {
newData.push(resource);
console.log('[useNormalizedCache] ✓ Appended new resource:', resource.name);
} else {
console.log('[useNormalizedCache] ✗ Rejected resource (filter returned false):', resource.name);
}
}
}