count pages per version

This commit is contained in:
2025-10-17 17:34:23 +02:00
parent 5666dc4a4b
commit d003f0783d
4 changed files with 121 additions and 12 deletions
+2
View File
@@ -87,6 +87,7 @@ pub struct DocumentVersion {
pub size_bytes: i64,
pub checksum: String,
pub created_at: NaiveDateTime,
pub metadata: serde_json::Value,
pub operations_summary: serde_json::Value,
}
@@ -99,6 +100,7 @@ pub struct NewDocumentVersion {
pub s3_key: String,
pub size_bytes: i64,
pub checksum: String,
pub metadata: serde_json::Value,
pub operations_summary: serde_json::Value,
}
+3
View File
@@ -107,6 +107,7 @@ pub struct DocumentVersionResponse {
pub size_bytes: i64,
pub checksum: String,
pub created_at: String,
pub metadata: Value,
pub operations_summary: Value,
}
@@ -1641,6 +1642,7 @@ async fn process_upload(
s3_key: s3_key.clone(),
size_bytes,
checksum: checksum_hex.clone(),
metadata: Value::Object(Default::default()),
operations_summary: Value::Object(Default::default()),
};
@@ -1871,6 +1873,7 @@ fn to_version_response(version: DocumentVersion) -> DocumentVersionResponse {
size_bytes: version.size_bytes,
checksum: version.checksum,
created_at: to_iso(version.created_at),
metadata: version.metadata,
operations_summary: version.operations_summary,
}
}
+1
View File
@@ -55,6 +55,7 @@ diesel::table! {
checksum -> Varchar,
created_at -> Timestamptz,
operations_summary -> Jsonb,
metadata -> Jsonb,
}
}
+115 -12
View File
@@ -1,4 +1,4 @@
use std::{io::Cursor, panic, sync::Arc, time::Duration};
use std::{convert::TryInto, io::Cursor, panic, sync::Arc, time::Duration};
use async_trait::async_trait;
use chrono::Utc;
@@ -6,7 +6,7 @@ use diesel::{pg::upsert::excluded, prelude::*};
use image::{GenericImageView, ImageFormat, ImageReader};
use pdfium_render::prelude::*;
use serde::Deserialize;
use serde_json::json;
use serde_json::{json, Map, Value};
use tokio::task;
use tracing::{error, info, warn};
use uuid::Uuid;
@@ -103,6 +103,45 @@ impl JobHandler for GenerateThumbnailsJob {
}
};
if let Some(page_count) = generation.page_count {
let state_clone = state.clone();
let document_id = initial.document.id;
let version_id = initial.version.id;
match task::spawn_blocking(move || {
persist_document_page_count(state_clone, document_id, version_id, page_count)
})
.await
{
Ok(Ok(())) => {}
Ok(Err(err)) => {
warn!(
job_id = %job.id,
document_id = %document_id,
version_id = %version_id,
error = %err,
"failed to update document page count metadata; retrying"
);
return JobExecution::Retry {
delay: Duration::from_secs(30),
error: err,
};
}
Err(join_err) => {
error!(
job_id = %job.id,
document_id = %document_id,
version_id = %version_id,
error = %join_err,
"page count metadata task panicked"
);
return JobExecution::Retry {
delay: Duration::from_secs(60),
error: format!("metadata panic: {join_err}"),
};
}
}
}
let thumbnail_asset_id = initial
.existing_thumbnail
.as_ref()
@@ -224,6 +263,7 @@ struct GeneratedImage {
struct GeneratedAssets {
thumbnail: GeneratedImage,
preview: GeneratedImage,
page_count: Option<u32>,
}
struct AssetPersistence<'a> {
@@ -305,13 +345,21 @@ fn generate_preview_and_thumbnail(
.unwrap_or(false)
});
let (preview, thumbnail) = if is_pdf {
generate_pdf_assets(bytes)?
if is_pdf {
let pdf_assets = generate_pdf_assets(bytes)?;
Ok(GeneratedAssets {
preview: pdf_assets.preview,
thumbnail: pdf_assets.thumbnail,
page_count: Some(pdf_assets.page_count),
})
} else {
generate_image_assets(bytes)?
};
Ok(GeneratedAssets { preview, thumbnail })
let (preview, thumbnail) = generate_image_assets(bytes)?;
Ok(GeneratedAssets {
preview,
thumbnail,
page_count: None,
})
}
}
fn generate_image_assets(bytes: &[u8]) -> Result<(GeneratedImage, GeneratedImage), String> {
@@ -339,7 +387,13 @@ fn generate_image_assets(bytes: &[u8]) -> Result<(GeneratedImage, GeneratedImage
Ok((preview, thumbnail))
}
fn generate_pdf_assets(bytes: &[u8]) -> Result<(GeneratedImage, GeneratedImage), String> {
struct PdfGeneratedAssets {
preview: GeneratedImage,
thumbnail: GeneratedImage,
page_count: u32,
}
fn generate_pdf_assets(bytes: &[u8]) -> Result<PdfGeneratedAssets, String> {
let pdfium = panic::catch_unwind(|| Pdfium::default())
.map_err(|_| "failed to initialize PDFium".to_string())?;
@@ -347,8 +401,10 @@ fn generate_pdf_assets(bytes: &[u8]) -> Result<(GeneratedImage, GeneratedImage),
.load_pdf_from_byte_slice(bytes, None)
.map_err(|err| format!("load pdf: {err}"))?;
let page = document
.pages()
let pages = document.pages();
let total_pages = pages.len();
let page = pages
.get(0)
.map_err(|err| format!("load first page: {err}"))?;
@@ -375,7 +431,15 @@ fn generate_pdf_assets(bytes: &[u8]) -> Result<(GeneratedImage, GeneratedImage),
let preview = encode_dynamic_image(preview_image)?;
let thumbnail = encode_dynamic_image(thumbnail_image)?;
Ok((preview, thumbnail))
let page_count: u32 = total_pages
.try_into()
.map_err(|_| "page count exceeds supported range".to_string())?;
Ok(PdfGeneratedAssets {
preview,
thumbnail,
page_count,
})
}
fn encode_dynamic_image(image: image::DynamicImage) -> Result<GeneratedImage, String> {
@@ -430,3 +494,42 @@ fn persist_assets_metadata(
Ok(())
}
fn persist_document_page_count(
state: Arc<AppState>,
document_id: Uuid,
document_version_id: Uuid,
page_count: u32,
) -> Result<(), String> {
let mut conn = state.db().map_err(|err| format!("{err:?}"))?;
let existing_metadata: Value = document_versions::table
.filter(document_versions::id.eq(document_version_id))
.filter(document_versions::document_id.eq(document_id))
.select(document_versions::metadata)
.first(&mut conn)
.map_err(|err| format!("{err:?}"))?;
let updated = match existing_metadata {
Value::Object(mut map) => {
map.insert("page_count".to_string(), Value::from(page_count));
Value::Object(map)
}
_ => {
let mut map = Map::new();
map.insert("page_count".to_string(), Value::from(page_count));
Value::Object(map)
}
};
diesel::update(
document_versions::table
.filter(document_versions::id.eq(document_version_id))
.filter(document_versions::document_id.eq(document_id)),
)
.set(document_versions::metadata.eq(updated))
.execute(&mut conn)
.map_err(|err| format!("{err:?}"))?;
Ok(())
}