refactor(lifecycle hooks): simplify integration of new services
* make more coherent lifecycles
* remove specific implementation on different handlers (they do not need to know existence of ThumbnailSerice nor AudioMetadataService)
* reduce risk of orphean objects
* ensure additional services are correctly wired (ex: Thumbnail generation was not covering all upload cases)
* more details on docs/architecture/file-and-blob-lifecycle.md :
```rust
// application/ports/file_lifecycle.rs
pub trait FileLifecycleHook {
fn on_file_created(file_id, blob_hash, content_type, is_new_blob);
fn on_file_updated(file_id, blob_hash, content_type);
fn on_file_copied(file_id, blob_hash, content_type, source_id)
fn on_file_deleted(file_id);
}
// application/ports/blob_lifecycle.rs
pub trait BlobLifecycleHook {
fn on_blob_created(blob_hash, content_type);
fn on_blob_deleted(blob_hash);
}
```
This commit is contained in:
+33
-22
@@ -42,6 +42,7 @@ use crate::infrastructure::services::pg_acl_engine::PgAclEngine;
|
||||
use crate::infrastructure::services::trash_cleanup_service::TrashCleanupService;
|
||||
|
||||
use crate::application::services::app_password_service::AppPasswordService;
|
||||
use crate::application::services::blob_lifecycle_service::BlobLifecycleService;
|
||||
use crate::application::services::calendar_service::CalendarService;
|
||||
use crate::application::services::device_auth_service::DeviceAuthService;
|
||||
use crate::application::services::file_lifecycle_service::FileLifecycleService;
|
||||
@@ -260,6 +261,12 @@ impl AppServiceFactory {
|
||||
tracing::info!("Blob storage LRU disk cache enabled");
|
||||
}
|
||||
|
||||
// Blob lifecycle — thumbnail disk-file cleanup when blob ref_count hits zero.
|
||||
// ThumbnailService (not ThumbnailRefreshHook) is used here to avoid a circular
|
||||
// Arc: DedupService→BlobLifecycleService→ThumbnailRefreshHook→DedupService.
|
||||
let blob_lifecycle =
|
||||
Arc::new(BlobLifecycleService::new().with_hook(thumbnail_service.clone()));
|
||||
|
||||
// Deduplication service — PRIMARY blob storage engine (PostgreSQL-backed index)
|
||||
let dedup_service = Arc::new(
|
||||
crate::infrastructure::services::dedup_service::DedupService::new(
|
||||
@@ -267,7 +274,7 @@ impl AppServiceFactory {
|
||||
db_pool.clone(),
|
||||
maintenance_pool.clone(),
|
||||
)
|
||||
.add_blob_hook(thumbnail_service.clone()),
|
||||
.with_blob_lifecycle(blob_lifecycle),
|
||||
);
|
||||
dedup_service.initialize().await?;
|
||||
|
||||
@@ -275,14 +282,30 @@ impl AppServiceFactory {
|
||||
"Core services initialized: path service, file content cache, thumbnails, chunked upload, image transcode, dedup (PRIMARY blob storage)"
|
||||
);
|
||||
|
||||
let file_lifecycle =
|
||||
Arc::new(FileLifecycleService::new().with_deleted_hook(thumbnail_service.clone()));
|
||||
// Audio metadata service — created here so it can be wired into file_lifecycle.
|
||||
let audio_metadata_service = self.create_audio_metadata_service(db_pool);
|
||||
|
||||
// ThumbnailRefreshHook: handles FileLifecycleHook events (create/update/delete).
|
||||
// Implemented on ThumbnailRefreshHook (not ThumbnailService) to avoid circular Arc:
|
||||
// DedupService → BlobLifecycleService → ThumbnailRefreshHook → DedupService.
|
||||
let thumbnail_refresh_hook = Arc::new(ThumbnailRefreshHook::new(
|
||||
thumbnail_service.clone(),
|
||||
dedup_service.clone(),
|
||||
));
|
||||
|
||||
// Build the unified FileLifecycleService dispatcher.
|
||||
let mut fls = FileLifecycleService::new().with_hook(thumbnail_refresh_hook);
|
||||
if let Some(audio) = &audio_metadata_service {
|
||||
fls = fls.with_hook(audio.clone());
|
||||
}
|
||||
let file_lifecycle = Arc::new(fls);
|
||||
|
||||
Ok(CoreServices {
|
||||
path_service,
|
||||
file_content_cache,
|
||||
thumbnail_service,
|
||||
file_lifecycle,
|
||||
audio_metadata_service,
|
||||
chunked_upload_service,
|
||||
image_transcode_service,
|
||||
dedup_service,
|
||||
@@ -355,7 +378,6 @@ impl AppServiceFactory {
|
||||
core: &CoreServices,
|
||||
repos: &RepositoryServices,
|
||||
trash_service: Option<Arc<TrashService>>,
|
||||
db_pool: &Arc<PgPool>,
|
||||
authz: &Arc<PgAclEngine>,
|
||||
) -> ApplicationServices {
|
||||
// Main services
|
||||
@@ -364,20 +386,13 @@ impl AppServiceFactory {
|
||||
authz.clone(),
|
||||
));
|
||||
|
||||
// Refactored services with all infrastructure ports
|
||||
// In blob model, dedup is handled by the repository — no separate write-behind needed
|
||||
let thumbnail_refresh_hook = Arc::new(ThumbnailRefreshHook::new(
|
||||
core.thumbnail_service.clone(),
|
||||
core.dedup_service.clone(),
|
||||
));
|
||||
let file_upload_service = Arc::new(
|
||||
FileUploadService::new_with_read(
|
||||
repos.file_write_repository.clone(),
|
||||
repos.file_read_repository.clone(),
|
||||
)
|
||||
.with_content_cache(core.file_content_cache.clone())
|
||||
.with_file_created_hook(thumbnail_refresh_hook.clone())
|
||||
.with_file_updated_hook(thumbnail_refresh_hook),
|
||||
.with_file_lifecycle_hook(core.file_lifecycle.clone()),
|
||||
);
|
||||
|
||||
let file_retrieval_service = Arc::new(FileRetrievalService::new_with_cache(
|
||||
@@ -397,7 +412,7 @@ impl AppServiceFactory {
|
||||
Some(core.file_content_cache.clone()),
|
||||
authz.clone(),
|
||||
)
|
||||
.with_file_deleted_hook(core.file_lifecycle.clone()),
|
||||
.with_file_lifecycle_hook(core.file_lifecycle.clone()),
|
||||
);
|
||||
|
||||
let file_use_case_factory = Arc::new(AppFileUseCaseFactory::new(
|
||||
@@ -433,7 +448,7 @@ impl AppServiceFactory {
|
||||
share_service: None, // Configured later with create_share_service
|
||||
favorites_service: None, // Configured later with create_favorites_service
|
||||
recent_service: None, // Configured later with create_recent_service
|
||||
audio_metadata_service: self.create_audio_metadata_service(db_pool),
|
||||
audio_metadata_service: core.audio_metadata_service.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,13 +648,8 @@ impl AppServiceFactory {
|
||||
.await;
|
||||
|
||||
// 4. Application services (with trash + authz already wired)
|
||||
let mut apps = self.create_application_services(
|
||||
&core,
|
||||
&repos,
|
||||
trash_service.clone(),
|
||||
&pool,
|
||||
&authorization,
|
||||
);
|
||||
let mut apps =
|
||||
self.create_application_services(&core, &repos, trash_service.clone(), &authorization);
|
||||
|
||||
// 5. Share service
|
||||
let share_service = self.create_share_service(&repos, &pool);
|
||||
@@ -1028,8 +1038,9 @@ pub struct CoreServices {
|
||||
pub path_service: Arc<PathService>,
|
||||
pub file_content_cache: Arc<FileContentCache>,
|
||||
pub thumbnail_service: Arc<ThumbnailService>,
|
||||
/// Composite lifecycle dispatcher — register new permanent-delete hooks here only.
|
||||
/// Composite lifecycle dispatcher — wires thumbnails + audio metadata for all file events.
|
||||
pub file_lifecycle: Arc<FileLifecycleService>,
|
||||
pub audio_metadata_service: Option<Arc<AudioMetadataService>>,
|
||||
pub chunked_upload_service: Arc<ChunkedUploadService>,
|
||||
pub image_transcode_service: Arc<ImageTranscodeService>,
|
||||
pub dedup_service: Arc<DedupService>,
|
||||
|
||||
Reference in New Issue
Block a user