count pages per version
This commit is contained in:
@@ -87,6 +87,7 @@ pub struct DocumentVersion {
|
|||||||
pub size_bytes: i64,
|
pub size_bytes: i64,
|
||||||
pub checksum: String,
|
pub checksum: String,
|
||||||
pub created_at: NaiveDateTime,
|
pub created_at: NaiveDateTime,
|
||||||
|
pub metadata: serde_json::Value,
|
||||||
pub operations_summary: serde_json::Value,
|
pub operations_summary: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +100,7 @@ pub struct NewDocumentVersion {
|
|||||||
pub s3_key: String,
|
pub s3_key: String,
|
||||||
pub size_bytes: i64,
|
pub size_bytes: i64,
|
||||||
pub checksum: String,
|
pub checksum: String,
|
||||||
|
pub metadata: serde_json::Value,
|
||||||
pub operations_summary: serde_json::Value,
|
pub operations_summary: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ pub struct DocumentVersionResponse {
|
|||||||
pub size_bytes: i64,
|
pub size_bytes: i64,
|
||||||
pub checksum: String,
|
pub checksum: String,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
|
pub metadata: Value,
|
||||||
pub operations_summary: Value,
|
pub operations_summary: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1641,6 +1642,7 @@ async fn process_upload(
|
|||||||
s3_key: s3_key.clone(),
|
s3_key: s3_key.clone(),
|
||||||
size_bytes,
|
size_bytes,
|
||||||
checksum: checksum_hex.clone(),
|
checksum: checksum_hex.clone(),
|
||||||
|
metadata: Value::Object(Default::default()),
|
||||||
operations_summary: 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,
|
size_bytes: version.size_bytes,
|
||||||
checksum: version.checksum,
|
checksum: version.checksum,
|
||||||
created_at: to_iso(version.created_at),
|
created_at: to_iso(version.created_at),
|
||||||
|
metadata: version.metadata,
|
||||||
operations_summary: version.operations_summary,
|
operations_summary: version.operations_summary,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ diesel::table! {
|
|||||||
checksum -> Varchar,
|
checksum -> Varchar,
|
||||||
created_at -> Timestamptz,
|
created_at -> Timestamptz,
|
||||||
operations_summary -> Jsonb,
|
operations_summary -> Jsonb,
|
||||||
|
metadata -> Jsonb,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 async_trait::async_trait;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@@ -6,7 +6,7 @@ use diesel::{pg::upsert::excluded, prelude::*};
|
|||||||
use image::{GenericImageView, ImageFormat, ImageReader};
|
use image::{GenericImageView, ImageFormat, ImageReader};
|
||||||
use pdfium_render::prelude::*;
|
use pdfium_render::prelude::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::{json, Map, Value};
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
use uuid::Uuid;
|
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
|
let thumbnail_asset_id = initial
|
||||||
.existing_thumbnail
|
.existing_thumbnail
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -224,6 +263,7 @@ struct GeneratedImage {
|
|||||||
struct GeneratedAssets {
|
struct GeneratedAssets {
|
||||||
thumbnail: GeneratedImage,
|
thumbnail: GeneratedImage,
|
||||||
preview: GeneratedImage,
|
preview: GeneratedImage,
|
||||||
|
page_count: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AssetPersistence<'a> {
|
struct AssetPersistence<'a> {
|
||||||
@@ -305,13 +345,21 @@ fn generate_preview_and_thumbnail(
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
let (preview, thumbnail) = if is_pdf {
|
if is_pdf {
|
||||||
generate_pdf_assets(bytes)?
|
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 {
|
} else {
|
||||||
generate_image_assets(bytes)?
|
let (preview, thumbnail) = generate_image_assets(bytes)?;
|
||||||
};
|
Ok(GeneratedAssets {
|
||||||
|
preview,
|
||||||
Ok(GeneratedAssets { preview, thumbnail })
|
thumbnail,
|
||||||
|
page_count: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_image_assets(bytes: &[u8]) -> Result<(GeneratedImage, GeneratedImage), String> {
|
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))
|
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())
|
let pdfium = panic::catch_unwind(|| Pdfium::default())
|
||||||
.map_err(|_| "failed to initialize PDFium".to_string())?;
|
.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)
|
.load_pdf_from_byte_slice(bytes, None)
|
||||||
.map_err(|err| format!("load pdf: {err}"))?;
|
.map_err(|err| format!("load pdf: {err}"))?;
|
||||||
|
|
||||||
let page = document
|
let pages = document.pages();
|
||||||
.pages()
|
let total_pages = pages.len();
|
||||||
|
|
||||||
|
let page = pages
|
||||||
.get(0)
|
.get(0)
|
||||||
.map_err(|err| format!("load first page: {err}"))?;
|
.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 preview = encode_dynamic_image(preview_image)?;
|
||||||
let thumbnail = encode_dynamic_image(thumbnail_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> {
|
fn encode_dynamic_image(image: image::DynamicImage) -> Result<GeneratedImage, String> {
|
||||||
@@ -430,3 +494,42 @@ fn persist_assets_metadata(
|
|||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user