feat: add XMP writer plugin and enhance media upload configuration
This commit is contained in:
@@ -13,5 +13,6 @@ pub fn load_config() -> CoreResult<Config> {
|
||||
jwt_secret: "super_secret_jwt_key".to_string(),
|
||||
media_library_path: "media_library".to_string(),
|
||||
broker_url: "nats://localhost:4222".to_string(),
|
||||
max_upload_size_mb: Some(100),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use libertas_core::plugins::PluginContext;
|
||||
@@ -6,6 +6,7 @@ use libertas_infra::factory::{
|
||||
build_album_repository, build_database_pool, build_media_repository, build_user_repository,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use tokio::fs;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{config::load_config, plugin_manager::PluginManager};
|
||||
@@ -19,6 +20,11 @@ struct MediaJob {
|
||||
media_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MediaDeletedJob {
|
||||
storage_path: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
println!("Starting libertas worker...");
|
||||
@@ -46,25 +52,42 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
println!("Connected to NATS server at {}", config.broker_url);
|
||||
|
||||
let mut subscriber = nats_client
|
||||
let mut sub_new = nats_client
|
||||
.queue_subscribe("media.new", "media_processors".to_string())
|
||||
.await?;
|
||||
println!("Subscribed to 'media.new' queue");
|
||||
|
||||
while let Some(msg) = subscriber.next().await {
|
||||
let context = context.clone();
|
||||
let manager = plugin_manager.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process_job(msg, context, manager).await {
|
||||
eprintln!("Job failed: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
let mut sub_deleted = nats_client
|
||||
.queue_subscribe("media.deleted", "media_deleters".to_string())
|
||||
.await?;
|
||||
println!("Subscribed to 'media.deleted' queue");
|
||||
|
||||
Ok(())
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(msg) = sub_new.next() => {
|
||||
let context = context.clone();
|
||||
let manager = plugin_manager.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process_new_job(msg, context, manager).await {
|
||||
eprintln!("Job failed: {}", e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// --- ADD THIS BLOCK ---
|
||||
Some(msg) = sub_deleted.next() => {
|
||||
let context = context.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = process_deleted_job(msg, context).await {
|
||||
eprintln!("Deletion job failed: {}", e);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_job(
|
||||
async fn process_new_job(
|
||||
msg: async_nats::Message,
|
||||
context: Arc<PluginContext>,
|
||||
plugin_manager: Arc<PluginManager>,
|
||||
@@ -85,3 +108,17 @@ async fn process_job(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_deleted_job(
|
||||
msg: async_nats::Message,
|
||||
context: Arc<PluginContext>,
|
||||
) -> anyhow::Result<()> {
|
||||
let payload: MediaDeletedJob = serde_json::from_slice(&msg.payload)?;
|
||||
let file_path = PathBuf::from(&context.media_library_path).join(&payload.storage_path);
|
||||
let xmp_path = format!("{}.xmp", file_path.to_string_lossy());
|
||||
if let Err(e) = fs::remove_file(xmp_path).await {
|
||||
println!("Failed to delete XMP sidecar: {}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use libertas_core::{
|
||||
plugins::{MediaProcessorPlugin, PluginContext},
|
||||
};
|
||||
|
||||
use crate::plugins::exif_reader::ExifReaderPlugin;
|
||||
use crate::plugins::{exif_reader::ExifReaderPlugin, xmp_writer::XmpWriterPlugin};
|
||||
|
||||
pub struct PluginManager {
|
||||
plugins: Vec<Arc<dyn MediaProcessorPlugin>>,
|
||||
@@ -17,6 +17,8 @@ impl PluginManager {
|
||||
|
||||
plugins.push(Arc::new(ExifReaderPlugin));
|
||||
|
||||
plugins.push(Arc::new(XmpWriterPlugin));
|
||||
|
||||
println!("PluginManager loaded {} plugins", plugins.len());
|
||||
Self { plugins }
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod exif_reader;
|
||||
pub mod xmp_writer;
|
||||
|
||||
54
libertas_worker/src/plugins/xmp_writer.rs
Normal file
54
libertas_worker/src/plugins/xmp_writer.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use libertas_core::{
|
||||
error::{CoreError, CoreResult},
|
||||
models::Media,
|
||||
plugins::{MediaProcessorPlugin, PluginContext, PluginData},
|
||||
};
|
||||
use tokio::fs;
|
||||
use xmp_toolkit::XmpMeta;
|
||||
|
||||
pub struct XmpWriterPlugin;
|
||||
|
||||
#[async_trait]
|
||||
impl MediaProcessorPlugin for XmpWriterPlugin {
|
||||
fn name(&self) -> &'static str {
|
||||
"xmp_writer"
|
||||
}
|
||||
|
||||
async fn process(&self, media: &Media, context: &PluginContext) -> CoreResult<PluginData> {
|
||||
let fresh_media = context
|
||||
.media_repo
|
||||
.find_by_id(media.id)
|
||||
.await?
|
||||
.ok_or(CoreError::NotFound("Media".to_string(), media.id))?;
|
||||
|
||||
let file_path = PathBuf::from(&context.media_library_path).join(&fresh_media.storage_path);
|
||||
let xmp_path = format!("{}.xmp", file_path.to_string_lossy());
|
||||
|
||||
let mut xmp = XmpMeta::new()
|
||||
.map_err(|e| CoreError::Unknown(format!("Failed to create new XMP metadata: {}", e)))?;
|
||||
|
||||
xmp.set_property(
|
||||
"http://purl.org/dc/elements/1.1/",
|
||||
"description",
|
||||
&fresh_media.original_filename.into(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
CoreError::Unknown(format!("Failed to set description property in XMP: {}", e))
|
||||
})?;
|
||||
|
||||
if let Some(_location) = &fresh_media.extracted_location {
|
||||
// TODO: Set location properties in XMP
|
||||
}
|
||||
|
||||
let xmp_str = xmp.to_string();
|
||||
|
||||
fs::write(&xmp_path, xmp_str).await?;
|
||||
|
||||
Ok(PluginData {
|
||||
message: "XMP sidecar written successfully.".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user