agent_diva_files/hooks.rs
1//! # Hook System - 钩子系统的全面解析
2//!
3//! 本模块实现了一套完整的生命周期钩子系统,允许开发者在文件操作的各个阶段
4//! 插入自定义逻辑,实现诸如压缩、加密、权限检查、缓存等功能。
5//!
6//! ## 钩子系统架构
7//!
8//! 钩子系统由两部分组成:**钩子 trait** 和 **钩子注册器**。
9//!
10//! ### 1. 钩子 Trait(Hooks)
11//!
12//! 每种钩子 trait 定义了一个特定的扩展点:
13//!
14//! | Trait | 扩展点 | 用途 |
15//! |-------|--------|------|
16//! | [`StorageHook`] | 存储前/后 | 压缩、加密、病毒扫描、通知 |
17//! | [`ReadHook`] | 读取前/后 | 权限检查、解密、缓存 |
18//! | [`MetadataHook`] | 元数据提取/验证 | 自定义元数据提取、内容分析 |
19//! | [`CleanupHook`] | 清理前/后 | 自定义保留策略、日志记录 |
20//!
21//! ### 2. 钩子注册器(HookRegistry)
22//!
23//! [`HookRegistry`] 是中央注册表,负责:
24//! - 存储所有注册的钩子
25//! - 按照预定义的顺序执行钩子
26//! - 处理钩子返回的 [`HookAction`](继续、阻止、重试)
27//!
28//! ## 执行流程
29//!
30//! ### 存储流程
31//! ```text
32//! store(data, metadata)
33//! └─→ StorageHook::before_store (所有注册的钩子串行执行)
34//! ├─→ Hook 1: 可以修改数据、返回错误阻止存储
35//! ├─→ Hook 2: ...
36//! └─→ Hook N: ...
37//! └─→ [数据存储到磁盘]
38//! └─→ MetadataHook::extract_metadata (提取额外元数据)
39//! └─→ [写入索引数据库]
40//! └─→ StorageHook::after_store (所有注册的钩子串行执行)
41//! └─→ 通知、缓存更新等
42//! ```
43//!
44//! ### 读取流程
45//! ```text//! read(handle)
46//! └─→ ReadHook::before_read (权限检查等)
47//! ├─→ Hook 1: 检查权限、返回错误阻止读取
48//! └─→ Hook 2: ...
49//! └─→ [从磁盘读取数据]
50//! └─→ ReadHook::after_read (数据转换)
51//! ├─→ Hook 1: 解密数据
52//! └─→ Hook 2: 缓存数据、转换格式
53//! ```
54//!
55//! ### 清理流程
56//! ```text
57//! cleanup()
58//! └─→ 对于每个候选文件:
59//! ├─→ CleanupHook::should_cleanup (任一返回 false 则跳过)
60//! └─→ [物理删除文件]
61//! └─→ CleanupHook::after_cleanup (日志、通知)
62//! ```
63//!
64//! ## 使用示例
65//!
66//! ### 示例1: 简单的日志钩子
67//!
68//! ```rust,ignore
69//! use agent_diva_files::hooks::{HookRegistry, StorageHook, HookAction};
70//! use agent_diva_files::{FileMetadata, Result};
71//! use async_trait::async_trait;
72//!
73//! struct LoggingHook;
74//!
75//! #[async_trait]
76//! impl StorageHook for LoggingHook {
77//! async fn before_store(&self, data: &[u8], metadata: &FileMetadata) -> Result<HookAction> {
78//! println!("Storing file: {}", metadata.name);
79//! Ok(HookAction::Continue) // 允许存储继续
80//! }
81//!
82//! async fn after_store(&self, handle: &FileHandle) -> Result<()> {
83//! println!("Stored file: {}", handle.id);
84//! Ok(())
85//! }
86//! }
87//!
88//! // 注册钩子
89//! let mut registry = HookRegistry::new();
90//! registry.register_storage_hook(Box::new(LoggingHook));
91//! ```
92//!
93//! ### 示例2: 加密钩子
94//!
95//! ```rust,ignore
96//! use agent_diva_files::hooks::{HookRegistry, StorageHook, HookAction};
97//! use agent_diva_files::{FileMetadata, Result};
98//! use async_trait::async_trait;
99//!
100//! struct EncryptHook {
101//! key: Vec<u8>,
102//! }
103//!
104//! #[async_trait]
105//! impl StorageHook for EncryptHook {
106//! // 在存储前加密数据
107//! async fn before_store(&self, data: &[u8], _metadata: &FileMetadata) -> Result<HookAction> {
108//! let encrypted = encrypt(data, &self.key)?;
109//! // 返回 Modify 告诉系统使用修改后的数据
110//! Ok(HookAction::Modify(encrypted))
111//! }
112//! }
113//! ```
114//!
115//! ### 示例3: 权限检查钩子
116//!
117//! ```rust,ignore
118//! use agent_diva_files::hooks::{HookRegistry, ReadHook, HookAction};
119//! use agent_diva_files::Result;
120//! use async_trait::async_trait;
121//!
122//! struct PermissionCheck {
123//! allowed_users: Vec<String>,
124//! }
125//!
126//! #[async_trait]
127//! impl ReadHook for PermissionCheck {
128//! async fn before_read(&self, id: &str, requester: Option<&str>) -> Result<HookAction> {
129//! match requester {
130//! Some(user) if self.allowed_users.contains(&user.to_string()) => {
131//! Ok(HookAction::Continue)
132//! }
133//! _ => Err(FileError::Storage("Permission denied".to_string())),
134//! }
135//! }
136//! }
137//! ```
138
139use crate::handle::{FileHandle, FileMetadata};
140use crate::Result;
141use async_trait::async_trait;
142
143// ============================================================================
144// 钩子动作 - HookAction
145// ============================================================================
146
147/// 钩子执行后返回的动作指令
148///
149/// 当一个钩子执行完毕后,它返回一个 `HookAction` 来告诉系统如何继续处理。
150/// 系统会按照以下优先级处理:
151/// 1. 如果任何 `before_*` 钩子返回 `Stop`,整个操作被阻止
152/// 2. 如果任何 `before_*` 钩子返回 `Error`,整个操作失败
153/// 3. 只有所有 `before_*` 钩子都返回 `Continue` 或 `Modify` 时,操作才会继续
154///
155/// # 变体说明
156///
157/// - `Continue`: 继续执行,不修改数据。这是大多数钩子的默认返回值。
158///
159/// - `Modify(data)`: 继续执行,但使用修改后的数据。
160/// 只有 `before_store` 和 `after_read` 支持此变体。
161/// 例如:加密钩子在 `before_store` 返回 `Modify(encrypted_data)`
162///
163/// - `Stop`: 立即停止执行,但不算错误。
164/// 例如:缓存钩子发现数据已在缓存中,返回 `Stop` 避免重复读取
165///
166/// - `Error(err)`: 立即停止并返回错误。
167/// 例如:权限检查发现无权访问时返回此值
168#[derive(Debug, Clone)]
169pub enum HookAction {
170 /// 继续执行下一个钩子或操作
171 Continue,
172
173 /// 继续执行,但使用修改后的数据
174 /// (仅用于 before_store 和 after_read)
175 Modify(Vec<u8>),
176
177 /// 立即停止执行(不算错误)
178 Stop,
179
180 /// 立即停止并返回错误
181 Error(String),
182}
183
184impl HookAction {
185 /// 判断是否应该继续执行
186 ///
187 /// 注意:`Modify` 也会继续执行,只是会携带修改后的数据
188 pub fn should_continue(&self) -> bool {
189 matches!(self, HookAction::Continue | HookAction::Modify(_))
190 }
191
192 /// 判断是否携带了修改后的数据
193 pub fn has_modified_data(&self) -> bool {
194 matches!(self, HookAction::Modify(_))
195 }
196
197 /// 获取修改后的数据(如果有)
198 pub fn get_modified_data(self) -> Option<Vec<u8>> {
199 match self {
200 HookAction::Modify(data) => Some(data),
201 _ => None,
202 }
203 }
204}
205
206// ============================================================================
207// 存储钩子 - StorageHook
208// ============================================================================
209
210/// 存储钩子 - 拦截文件存储操作
211///
212/// 在文件存储到磁盘之前和之后执行自定义逻辑。
213///
214/// # 使用场景
215///
216/// - **数据压缩**: 在存储前压缩数据,节省空间
217/// - **数据加密**: 在存储前加密敏感数据
218/// - **病毒扫描**: 在存储前扫描恶意软件
219/// - **内容审核**: 检查内容是否符合政策
220/// - **存储通知**: 记录文件存储事件用于审计
221///
222/// # 执行时机
223///
224/// 1. `before_store` 在数据写入磁盘**之前**执行
225/// - 可以修改要存储的数据
226/// - 可以拒绝存储(返回 `HookAction::Error`)
227/// - 如果返回 `HookAction::Modify`,系统会存储修改后的数据
228///
229/// 2. `after_store` 在数据成功写入磁盘**之后**执行
230/// - 不能修改数据(此时数据已写入)
231/// - 适合做通知、缓存更新等操作
232///
233/// # 错误处理
234///
235/// 如果 `before_store` 返回错误,文件**不会被存储**,系统会立即返回错误。
236/// 如果 `after_store` 返回错误,文件**已经被存储**,错误会被记录但不会回滚。
237///
238/// # 示例:压缩钩子
239///
240/// ```rust,ignore
241/// use async_trait::async_trait;
242/// use agent_diva_files::hooks::{StorageHook, HookAction};
243/// use agent_diva_files::{FileMetadata, Result};
244///
245/// struct CompressionHook {
246///
247/// #[async_trait]
248/// impl StorageHook for CompressionHook {
249/// async fn before_store(&self, data: &[u8], metadata: &FileMetadata) -> Result<HookAction> {
250/// // 只压缩大于1MB的文件
251/// if data.len() > 1024 * 1024 {
252/// let compressed = compress(data)?;
253/// Ok(HookAction::Modify(compressed))
254/// } else {
255/// Ok(HookAction::Continue)
256/// }
257/// }
258///
259/// async fn after_store(&self, handle: &FileHandle) -> Result<()> {
260/// tracing::info!("Compressed file stored: {}", handle.id);
261/// Ok(())
262/// }
263/// }
264/// }
265/// ```
266#[async_trait]
267pub trait StorageHook: Send + Sync {
268 /// 存储前钩子 - 在数据写入磁盘之前调用
269 ///
270 /// # 参数
271 /// - `data`: 要存储的原始数据字节
272 /// - `metadata`: 文件元数据(包含文件名、大小、MIME类型等)
273 ///
274 /// # 返回值
275 /// - `Ok(HookAction::Continue)`: 继续存储,使用原始数据
276 /// - `Ok(HookAction::Modify(data))`: 继续存储,但使用修改后的数据
277 /// - `Ok(HookAction::Stop)`: 停止执行,但不报错(数据不会被存储)
278 /// - `Ok(HookAction::Error(msg))`: 停止执行,返回错误
279 ///
280 /// # 默认实现
281 /// 默认实现返回 `HookAction::Continue`,不在存储前做任何处理
282 async fn before_store(&self, _data: &[u8], _metadata: &FileMetadata) -> Result<HookAction> {
283 Ok(HookAction::Continue)
284 }
285
286 /// 存储后钩子 - 在数据成功写入磁盘之后调用
287 ///
288 /// # 参数
289 /// - `handle`: 文件句柄,包含文件ID、路径和元数据
290 ///
291 /// # 返回值
292 /// - `Ok(())`: 钩子执行成功
293 /// - `Err(e)`: 钩子执行失败(会被记录但不会影响已存储的文件)
294 ///
295 /// # 默认实现
296 /// 默认实现什么都不做,直接返回成功
297 async fn after_store(&self, _handle: &FileHandle) -> Result<()> {
298 Ok(())
299 }
300}
301
302// ============================================================================
303// 读取钩子 - ReadHook
304// ============================================================================
305
306/// 读取钩子 - 拦截文件读取操作
307///
308/// 在文件从磁盘读取之前和之后执行自定义逻辑。
309///
310/// # 使用场景
311///
312/// - **权限检查**: 验证请求者是否有权读取文件
313/// - **数据解密**: 读取后解密加密的数据
314/// - **缓存处理**: 读取后更新缓存、设置缓存策略
315/// - **内容转换**: 读取后转换数据格式(如图片缩放)
316/// - **访问统计**: 记录文件访问用于分析
317///
318/// # 执行时机
319///
320/// 1. `before_read` 在数据从磁盘读取**之前**执行
321/// - 适合做权限检查
322/// - 如果返回 `Stop`,可以避免实际的磁盘读取
323/// - 如果返回 `Error`,读取操作失败
324///
325/// 2. `after_read` 在数据从磁盘读取**之后**执行
326/// - 可以修改读取的数据
327/// - 适合做解密、缓存等操作
328///
329/// # 与存储钩子的对称性
330///
331/// 存储钩子和读取钩子常常成对出现:
332/// - `before_store` / `after_read`: 配对的加密/解密
333/// - `after_store` / `before_read`: 配对的权限检查
334///
335/// # 示例:解密钩子
336///
337/// ```rust,ignore
338/// use async_trait::async_trait;
339/// use agent_diva_files::hooks::{ReadHook, HookAction};
340/// use agent_diva_files::Result;
341///
342/// struct DecryptionHook {
343/// key: Vec<u8>,
344/// }
345///
346/// #[async_trait]
347/// impl ReadHook for DecryptionHook {
348/// async fn before_read(&self, _id: &str, _requester: Option<&str>) -> Result<HookAction> {
349/// // 权限检查可以在这里进行
350/// Ok(HookAction::Continue)
351/// }
352///
353/// async fn after_read(&self, data: &[u8]) -> Result<HookAction> {
354/// // 解密数据
355/// let decrypted = decrypt(data, &self.key)?;
356/// Ok(HookAction::Modify(decrypted))
357/// }
358/// }
359/// ```
360#[async_trait]
361pub trait ReadHook: Send + Sync {
362 /// 读取前钩子 - 在数据从磁盘读取之前调用
363 ///
364 /// # 参数
365 /// - `id`: 文件的唯一标识符(SHA256哈希)
366 /// - `requester`: 请求读取的用户标识(如果有)
367 ///
368 /// # 返回值
369 /// - `Ok(HookAction::Continue)`: 继续读取
370 /// - `Ok(HookAction::Stop)`: 停止执行(数据未读取)
371 /// - `Ok(HookAction::Error(msg))`: 读取失败
372 ///
373 /// # 默认实现
374 /// 默认实现不做任何检查,直接返回继续
375 async fn before_read(&self, _id: &str, _requester: Option<&str>) -> Result<HookAction> {
376 Ok(HookAction::Continue)
377 }
378
379 /// 读取后钩子 - 在数据从磁盘读取之后调用
380 ///
381 /// # 参数
382 /// - `data`: 从磁盘读取的原始数据
383 ///
384 /// # 返回值
385 /// - `Ok(HookAction::Continue)`: 使用原始数据
386 /// - `Ok(HookAction::Modify(data))`: 使用修改后的数据
387 /// - `Err(e)`: 处理失败
388 ///
389 /// # 默认实现
390 /// 默认实现不做任何处理,直接返回原始数据
391 async fn after_read(&self, data: &[u8]) -> Result<HookAction> {
392 Ok(HookAction::Modify(data.to_vec()))
393 }
394}
395
396// ============================================================================
397// 元数据钩子 - MetadataHook
398// ============================================================================
399
400/// 元数据钩子 - 自定义元数据提取和验证
401///
402/// 允许在标准元数据之外提取额外的文件属性。
403///
404/// # 使用场景
405///
406/// - **内容分析**: 从文件内容中提取额外信息(如图片尺寸、文档页数)
407/// - **标签生成**: 基于内容自动生成标签
408/// - **格式检测**: 检测并记录精确的MIME类型
409/// - **完整性验证**: 验证文件内容与声称的格式是否匹配
410///
411/// # 示例:图片尺寸提取钩子
412///
413/// ```rust,ignore
414/// use async_trait::async_trait;
415/// use agent_diva_files::hooks::MetadataHook;
416/// use agent_diva_files::{FileMetadata, Result};
417///
418/// struct ImageDimensionsHook;
419///
420/// #[async_trait]
421/// impl MetadataHook for ImageDimensionsHook {
422/// async fn extract_metadata(&self, data: &[u8], base: &FileMetadata) -> Result<serde_json::Value> {
423/// if let Some(dimensions) = extract_image_dimensions(data)? {
424/// Ok(serde_json::json!({
425/// "width": dimensions.0,
426/// "height": dimensions.1,
427/// }))
428/// } else {
429/// Ok(serde_json::Value::Null)
430/// }
431/// }
432///
433/// async fn validate_metadata(&self, metadata: &FileMetadata) -> Result<()> {
434/// // 验证MIME类型与实际内容是否匹配
435/// if metadata.name.ends_with(".png") && metadata.mime_type != Some("image/png".to_string()) {
436/// return Err(FileError::Storage("MIME type mismatch".to_string()));
437/// }
438/// Ok(())
439/// }
440/// }
441/// ```
442#[async_trait]
443pub trait MetadataHook: Send + Sync {
444 /// 提取额外元数据
445 ///
446 /// 在文件存储时调用,可以从内容中提取额外信息。
447 ///
448 /// # 参数
449 /// - `data`: 文件的原始数据
450 /// - `base`: 基础元数据(已有文件名、大小等)
451 ///
452 /// # 返回值
453 /// 返回一个 JSON Value,会被合并到文件的 `metadata.extra` 字段
454 ///
455 /// # 默认实现
456 /// 默认返回 `serde_json::Value::Null`,不添加额外信息
457 async fn extract_metadata(
458 &self,
459 _data: &[u8],
460 _base: &FileMetadata,
461 ) -> Result<serde_json::Value> {
462 Ok(serde_json::Value::Null)
463 }
464
465 /// 验证元数据
466 ///
467 /// 在文件存储前调用,验证元数据的有效性。
468 ///
469 /// # 参数
470 /// - `metadata`: 要验证的元数据
471 ///
472 /// # 返回值
473 /// - `Ok(())`: 验证通过
474 /// - `Err(e)`: 验证失败,文件不会被存储
475 ///
476 /// # 默认实现
477 /// 默认实现不做验证,总是返回成功
478 async fn validate_metadata(&self, _metadata: &FileMetadata) -> Result<()> {
479 Ok(())
480 }
481}
482
483// ============================================================================
484// 清理钩子 - CleanupHook
485// ============================================================================
486
487/// 清理钩子 - 自定义文件清理策略
488///
489/// 控制在什么条件下文件可以被清理删除。
490///
491/// # 使用场景
492///
493/// - **自定义保留策略**: 如只保留最近7天被访问过的文件
494/// - **保护重要文件**: 标记某些文件为"永不清理"
495/// - **清理前通知**: 在删除前发送通知
496/// - **清理后处理**: 删除后清理相关缓存、关联数据
497///
498/// # 执行时机
499///
500/// 1. `should_cleanup` 在文件**实际删除之前**调用
501/// - 返回 `true` 允许删除
502/// - 返回 `false` 跳过此文件
503/// - 返回错误也会阻止删除
504///
505/// 2. `after_cleanup` 在文件**实际删除之后**调用
506/// - 常用于清理相关资源
507///
508/// # 重要:与软删除的关系
509///
510/// 清理钩子工作在**物理删除**层面:
511/// - 软删除文件由 `soft_delete()` 处理,有自己的保留期
512/// - 清理钩子只在文件**真正从数据库删除**时触发
513///
514/// # 示例:只清理超过30天未访问的文件
515///
516/// ```rust,ignore
517/// use async_trait::async_trait;
518/// use agent_diva_files::hooks::CleanupHook;
519/// use agent_diva_files::{FileIndexEntry, Result};
520/// use chrono::{DateTime, Utc};
521///
522/// struct CustomRetentionHook {
523/// min_days_since_access: i64,
524/// }
525///
526/// #[async_trait]
527/// impl CleanupHook for CustomRetentionHook {
528/// async fn should_cleanup(&self, entry: &FileIndexEntry) -> Result<bool> {
529/// if let Some(last_access) = entry.last_accessed_at {
530/// let days_since = (Utc::now() - last_access).num_days();
531/// Ok(days_since > self.min_days_since_access)
532/// } else {
533/// // 从未被访问过,检查创建时间
534/// let days_since = (Utc::now() - entry.created_at).num_days();
535/// Ok(days_since > self.min_days_since_access)
536/// }
537/// }
538/// }
539/// ```
540#[async_trait]
541pub trait CleanupHook: Send + Sync {
542 /// 清理前检查 - 决定是否应该删除文件
543 ///
544 /// # 参数
545 /// - `entry`: 文件索引条目,包含文件的完整信息
546 ///
547 /// # 返回值
548 /// - `Ok(true)`: 允许清理,系统会删除文件
549 /// - `Ok(false)`: 跳过此文件,不删除
550 /// - `Err(e)`: 阻止清理,返回错误
551 ///
552 /// # 默认实现
553 /// 默认实现总是返回 `true`,允许清理所有候选文件
554 async fn should_cleanup(&self, _entry: &crate::handle::FileIndexEntry) -> Result<bool> {
555 Ok(true)
556 }
557
558 /// 清理后钩子 - 文件被删除后调用
559 ///
560 /// # 参数
561 /// - `entry`: 被删除文件的原始条目信息
562 ///
563 /// # 返回值
564 /// - `Ok(())`: 清理后处理成功
565 /// - `Err(e)`: 清理后处理失败(会被记录但不会回滚删除)
566 ///
567 /// # 默认实现
568 /// 默认实现什么都不做
569 async fn after_cleanup(&self, _entry: &crate::handle::FileIndexEntry) -> Result<()> {
570 Ok(())
571 }
572}
573
574// ============================================================================
575// 钩子注册器 - HookRegistry
576// ============================================================================
577
578/// 钩子注册器 - 统一管理所有类型的钩子
579///
580/// `HookRegistry` 是钩子系统的中央组件,负责:
581/// - 存储所有注册的钩子实例
582/// - 按顺序执行钩子
583/// - 协调各类型钩子的执行
584///
585/// # 线程安全
586///
587/// `HookRegistry` 内部使用 `std::sync::Mutex` 保护状态,
588/// 因此可以在多线程环境中安全使用。
589///
590/// # 示例:完整的钩子注册流程
591///
592/// ```rust,ignore
593/// use agent_diva_files::hooks::{HookRegistry, StorageHook, ReadHook, CleanupHook};
594/// use agent_diva_files::hooks::CompressionHook;
595/// use agent_diva_files::FileManager;
596///
597/// // 创建注册器
598/// let mut registry = HookRegistry::new();
599///
600/// // 注册存储钩子
601/// registry.register_storage_hook(Box::new(CompressionHook::new()));
602///
603/// // 注册读取钩子
604/// registry.register_read_hook(Box::new(DecryptionHook::new()));
605///
606/// // 注册清理钩子
607/// registry.register_cleanup_hook(Box::new(RetentionPolicyHook::new()));
608///
609/// // 将注册器传给 FileManager
610/// let manager = FileManager::new(config, registry).await?;
611/// ```
612///
613/// # 执行顺序
614///
615/// 当多个同类型钩子注册时,它们按照**注册顺序**串行执行。
616/// 前一个钩子的输出(修改后的数据)会传给下一个钩子作为输入。
617///
618/// 例如:注册了 [HookA, HookB],存储流程是:
619/// ```text
620/// data → HookA.before_store → HookB.before_store → [存储]
621/// ```
622#[derive(Default)]
623pub struct HookRegistry {
624 /// 已注册的存储钩子列表
625 storage_hooks: Vec<Box<dyn StorageHook>>,
626
627 /// 已注册的读取钩子列表
628 read_hooks: Vec<Box<dyn ReadHook>>,
629
630 /// 已注册的元数据钩子列表
631 metadata_hooks: Vec<Box<dyn MetadataHook>>,
632
633 /// 已注册的清理钩子列表
634 cleanup_hooks: Vec<Box<dyn CleanupHook>>,
635}
636
637impl std::fmt::Debug for HookRegistry {
638 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
639 f.debug_struct("HookRegistry")
640 .field("storage_hooks", &self.storage_hooks.len())
641 .field("read_hooks", &self.read_hooks.len())
642 .field("metadata_hooks", &self.metadata_hooks.len())
643 .field("cleanup_hooks", &self.cleanup_hooks.len())
644 .finish()
645 }
646}
647
648impl HookRegistry {
649 /// 创建新的空钩子注册器
650 pub fn new() -> Self {
651 Self::default()
652 }
653
654 // -------------------------------------------------------------------------
655 // 注册方法
656 // -------------------------------------------------------------------------
657
658 /// 注册一个存储钩子
659 ///
660 /// 存储钩子会在文件存储时被调用。
661 /// 可以注册多个存储钩子,它们会按注册顺序串行执行。
662 ///
663 /// # 参数
664 /// - `hook`: 实现 `StorageHook` trait 的 Box 指针
665 ///
666 /// # 示例
667 /// ```rust,ignore
668 /// registry.register_storage_hook(Box::new(MyStorageHook));
669 /// ```
670 pub fn register_storage_hook(&mut self, hook: Box<dyn StorageHook>) {
671 self.storage_hooks.push(hook);
672 }
673
674 /// 注册一个读取钩子
675 ///
676 /// 读取钩子会在文件读取时被调用。
677 /// 可以注册多个读取钩子,它们会按注册顺序串行执行。
678 pub fn register_read_hook(&mut self, hook: Box<dyn ReadHook>) {
679 self.read_hooks.push(hook);
680 }
681
682 /// 注册一个元数据钩子
683 ///
684 /// 元数据钩子会在文件存储时提取和验证元数据。
685 /// 可以注册多个元数据钩子,提取的元数据会被合并。
686 pub fn register_metadata_hook(&mut self, hook: Box<dyn MetadataHook>) {
687 self.metadata_hooks.push(hook);
688 }
689
690 /// 注册一个清理钩子
691 ///
692 /// 清理钩子会在文件被清理删除时被调用。
693 /// 可以注册多个清理钩子,每个都会在清理前后被调用。
694 pub fn register_cleanup_hook(&mut self, hook: Box<dyn CleanupHook>) {
695 self.cleanup_hooks.push(hook);
696 }
697
698 // -------------------------------------------------------------------------
699 // 执行方法(由 FileManager 内部调用)
700 // -------------------------------------------------------------------------
701
702 /// 执行所有存储前钩子
703 ///
704 /// 依次执行每个已注册的 `before_store` 钩子。
705 /// 如果任何钩子返回 `Stop` 或 `Error`,立即停止。
706 /// 如果任何钩子返回 `Modify`,后续钩子会收到修改后的数据。
707 ///
708 /// # 参数
709 /// - `data`: 原始数据
710 /// - `metadata`: 文件元数据
711 ///
712 /// # 返回
713 /// - `Ok((data, should_continue))`: 继续执行,数据可能是修改后的
714 /// - `Err(e)`: 被某个钩子阻止
715 pub(crate) async fn run_before_store(
716 &self,
717 data: &[u8],
718 metadata: &FileMetadata,
719 ) -> Result<(Vec<u8>, bool)> {
720 let mut current_data = data.to_vec();
721 let mut should_continue = true;
722
723 for hook in &self.storage_hooks {
724 match hook.before_store(¤t_data, metadata).await? {
725 HookAction::Continue => {
726 // 继续,不修改数据
727 }
728 HookAction::Modify(new_data) => {
729 // 钩子返回了修改后的数据
730 current_data = new_data;
731 }
732 HookAction::Stop => {
733 // 钩子请求停止
734 should_continue = false;
735 break;
736 }
737 HookAction::Error(msg) => {
738 return Err(crate::FileError::Storage(msg));
739 }
740 }
741 }
742
743 Ok((current_data, should_continue))
744 }
745
746 /// 执行所有存储后钩子
747 ///
748 /// 依次执行每个已注册的 `after_store` 钩子。
749 /// 错误会被记录但不会影响已完成的存储操作。
750 pub(crate) async fn run_after_store(&self, handle: &FileHandle) {
751 for hook in &self.storage_hooks {
752 if let Err(e) = hook.after_store(handle).await {
753 tracing::warn!("Storage hook after_store error: {}", e);
754 }
755 }
756 }
757
758 /// 执行所有读取前钩子
759 ///
760 /// 依次执行每个已注册的 `before_read` 钩子。
761 /// 如果任何钩子返回 `Stop` 或 `Error`,立即停止。
762 pub(crate) async fn run_before_read(&self, id: &str, requester: Option<&str>) -> Result<bool> {
763 for hook in &self.read_hooks {
764 match hook.before_read(id, requester).await? {
765 HookAction::Continue => {}
766 HookAction::Stop => return Ok(false),
767 HookAction::Error(msg) => {
768 return Err(crate::FileError::Storage(msg));
769 }
770 HookAction::Modify(_) => {
771 // before_read 不应该返回 Modify,这是误用
772 tracing::warn!(
773 "Read hook returned Modify, which is not supported in before_read"
774 );
775 }
776 }
777 }
778 Ok(true)
779 }
780
781 /// 执行所有读取后钩子
782 ///
783 /// 依次执行每个已注册的 `after_read` 钩子。
784 /// 前一个钩子的输出会传给下一个钩子作为输入。
785 ///
786 /// # 返回
787 /// 最终的数据(可能经过多次转换)
788 pub(crate) async fn run_after_read(&self, data: &[u8]) -> Result<Vec<u8>> {
789 let mut current_data = data.to_vec();
790
791 for hook in &self.read_hooks {
792 match hook.after_read(¤t_data).await? {
793 HookAction::Continue => {
794 // 不修改,继续
795 }
796 HookAction::Modify(new_data) => {
797 current_data = new_data;
798 }
799 HookAction::Stop | HookAction::Error(_) => {
800 // after_read 不应该返回 Stop 或 Error
801 tracing::warn!("Read hook returned Stop/Error in after_read, which is ignored");
802 }
803 }
804 }
805
806 Ok(current_data)
807 }
808
809 /// 执行元数据提取钩子
810 ///
811 /// 依次执行每个已注册的 `extract_metadata` 钩子,
812 /// 提取的元数据会被合并到一个 JSON 对象中。
813 #[allow(dead_code)]
814 pub(crate) async fn run_extract_metadata(
815 &self,
816 data: &[u8],
817 base: &FileMetadata,
818 ) -> Result<serde_json::Value> {
819 let mut result = serde_json::json!({});
820
821 for hook in &self.metadata_hooks {
822 let extra = hook.extract_metadata(data, base).await?;
823 // 合并元数据(简单的浅合并)
824 if let serde_json::Value::Object(mut map) = extra {
825 if let serde_json::Value::Object(base_map) = serde_json::to_value(&result)? {
826 map.extend(base_map);
827 result = serde_json::Value::Object(map);
828 }
829 }
830 }
831
832 Ok(result)
833 }
834
835 /// 执行元数据验证钩子
836 ///
837 /// 依次执行每个已注册的 `validate_metadata` 钩子。
838 /// 如果任何钩子返回错误,立即停止并返回错误。
839 pub(crate) async fn run_validate_metadata(&self, metadata: &FileMetadata) -> Result<()> {
840 for hook in &self.metadata_hooks {
841 hook.validate_metadata(metadata).await?;
842 }
843 Ok(())
844 }
845
846 /// 执行清理前检查钩子
847 ///
848 /// 依次执行每个已注册的 `should_cleanup` 钩子。
849 /// 如果任何钩子返回 `false` 或错误,立即返回。
850 pub(crate) async fn run_should_cleanup(
851 &self,
852 entry: &crate::handle::FileIndexEntry,
853 ) -> Result<bool> {
854 for hook in &self.cleanup_hooks {
855 if !hook.should_cleanup(entry).await? {
856 return Ok(false);
857 }
858 }
859 Ok(true)
860 }
861
862 /// 执行清理后钩子
863 ///
864 /// 依次执行每个已注册的 `after_cleanup` 钩子。
865 /// 错误会被记录但不会回滚删除操作。
866 pub(crate) async fn run_after_cleanup(&self, entry: &crate::handle::FileIndexEntry) {
867 for hook in &self.cleanup_hooks {
868 if let Err(e) = hook.after_cleanup(entry).await {
869 tracing::warn!("Cleanup hook after_cleanup error: {}", e);
870 }
871 }
872 }
873
874 /// 获取已注册钩子的数量统计
875 ///
876 /// 用于调试和监控。
877 pub fn hook_counts(&self) -> HookCounts {
878 HookCounts {
879 storage: self.storage_hooks.len(),
880 read: self.read_hooks.len(),
881 metadata: self.metadata_hooks.len(),
882 cleanup: self.cleanup_hooks.len(),
883 }
884 }
885}
886
887/// 钩子数量统计
888#[derive(Debug, Clone, Default)]
889pub struct HookCounts {
890 /// 存储钩子数量
891 pub storage: usize,
892 /// 读取钩子数量
893 pub read: usize,
894 /// 元数据钩子数量
895 pub metadata: usize,
896 /// 清理钩子数量
897 pub cleanup: usize,
898}
899
900// ============================================================================
901// 预置钩子实现 - Built-in Hooks
902// ============================================================================
903
904// 预置钩子放在这里,方便用户直接使用而不需要自己实现
905
906/// 日志钩子 - 记录所有存储和读取操作
907///
908/// # 用法
909/// ```rust,ignore
910/// let mut registry = HookRegistry::new();
911/// registry.register_storage_hook(Box::new(LoggingStorageHook));
912/// registry.register_read_hook(Box::new(LoggingReadHook));
913/// ```
914pub struct LoggingStorageHook;
915
916#[async_trait]
917impl StorageHook for LoggingStorageHook {
918 async fn before_store(&self, data: &[u8], metadata: &FileMetadata) -> Result<HookAction> {
919 tracing::info!(
920 "Storage: storing file '{}' ({} bytes)",
921 metadata.name,
922 data.len()
923 );
924 Ok(HookAction::Continue)
925 }
926
927 async fn after_store(&self, handle: &FileHandle) -> Result<()> {
928 tracing::info!("Storage: file stored with ID '{}'", handle.id);
929 Ok(())
930 }
931}
932
933/// 日志读取钩子
934pub struct LoggingReadHook;
935
936#[async_trait]
937impl ReadHook for LoggingReadHook {
938 async fn before_read(&self, id: &str, requester: Option<&str>) -> Result<HookAction> {
939 tracing::debug!("Read: reading file '{}' (requested by {:?})", id, requester);
940 Ok(HookAction::Continue)
941 }
942
943 async fn after_read(&self, data: &[u8]) -> Result<HookAction> {
944 tracing::debug!("Read: read {} bytes", data.len());
945 Ok(HookAction::Modify(data.to_vec()))
946 }
947}
948
949/// 清理日志钩子 - 记录清理操作
950pub struct LoggingCleanupHook;
951
952#[async_trait]
953impl CleanupHook for LoggingCleanupHook {
954 async fn should_cleanup(&self, entry: &crate::handle::FileIndexEntry) -> Result<bool> {
955 tracing::debug!(
956 "Cleanup: checking if '{}' (ref_count={}) should be cleaned up",
957 entry.id,
958 entry.ref_count
959 );
960 Ok(true)
961 }
962
963 async fn after_cleanup(&self, entry: &crate::handle::FileIndexEntry) -> Result<()> {
964 tracing::info!("Cleanup: file '{}' has been deleted", entry.id);
965 Ok(())
966 }
967}
968
969// ============================================================================
970// 单元测试
971// ============================================================================
972
973#[cfg(test)]
974mod tests {
975 use super::*;
976
977 // 测试辅助:创建一个简单的存储钩子
978 struct TestStorageHook {
979 before_called: std::sync::Arc<std::sync::atomic::AtomicBool>,
980 after_called: std::sync::Arc<std::sync::atomic::AtomicBool>,
981 }
982
983 impl TestStorageHook {
984 fn new() -> (
985 Self,
986 std::sync::Arc<std::sync::atomic::AtomicBool>,
987 std::sync::Arc<std::sync::atomic::AtomicBool>,
988 ) {
989 let before_called = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
990 let after_called = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
991 let bc = before_called.clone();
992 let ac = after_called.clone();
993 (
994 Self {
995 before_called,
996 after_called,
997 },
998 bc,
999 ac,
1000 )
1001 }
1002 }
1003
1004 #[async_trait]
1005 impl StorageHook for TestStorageHook {
1006 async fn before_store(&self, _data: &[u8], metadata: &FileMetadata) -> Result<HookAction> {
1007 self.before_called
1008 .store(true, std::sync::atomic::Ordering::SeqCst);
1009 tracing::debug!("Test hook: before_store for '{}'", metadata.name);
1010 Ok(HookAction::Continue)
1011 }
1012
1013 async fn after_store(&self, handle: &FileHandle) -> Result<()> {
1014 self.after_called
1015 .store(true, std::sync::atomic::Ordering::SeqCst);
1016 tracing::debug!("Test hook: after_store for '{}'", handle.id);
1017 Ok(())
1018 }
1019 }
1020
1021 #[tokio::test]
1022 async fn test_hook_registry_register_and_count() {
1023 let mut registry = HookRegistry::new();
1024
1025 assert_eq!(registry.hook_counts().storage, 0);
1026
1027 registry.register_storage_hook(Box::new(TestStorageHook::new().0));
1028 assert_eq!(registry.hook_counts().storage, 1);
1029
1030 registry.register_read_hook(Box::new(TestReadHook));
1031 assert_eq!(registry.hook_counts().read, 1);
1032 }
1033
1034 #[tokio::test]
1035 async fn test_hook_action_modify_carries_data() {
1036 let action = HookAction::Modify(vec![1, 2, 3]);
1037
1038 assert!(action.should_continue());
1039 assert!(action.has_modified_data());
1040 assert_eq!(action.get_modified_data(), Some(vec![1, 2, 3]));
1041 }
1042
1043 #[tokio::test]
1044 async fn test_hook_action_stop_does_not_continue() {
1045 let action = HookAction::Stop;
1046 assert!(!action.should_continue());
1047 assert!(!action.has_modified_data());
1048 }
1049
1050 // 测试辅助:简单的读取钩子
1051 struct TestReadHook;
1052
1053 #[async_trait]
1054 impl ReadHook for TestReadHook {
1055 async fn before_read(&self, _id: &str, _requester: Option<&str>) -> Result<HookAction> {
1056 Ok(HookAction::Continue)
1057 }
1058
1059 async fn after_read(&self, data: &[u8]) -> Result<HookAction> {
1060 Ok(HookAction::Modify(data.to_vec()))
1061 }
1062 }
1063}