rusty_cat/upload_trait.rs
1use async_trait::async_trait;
2use crate::http_breakpoint::UploadResumeInfo;
3use crate::{MeowError, TransferTask};
4
5/// 自定义断点上传协议。
6///
7/// 实现方负责:按业务约定构造 HTTP 请求(multipart 或二进制体)、解析响应体为 [`UploadResumeInfo`]。
8/// 执行器负责:打开本地文件、按 [`TransferTask`] 的 `chunk_size` 读分片、并发/重试/进度/暂停恢复;
9/// 每次上传前会调用 [`BreakpointUpload::prepare`],随后对每个分片调用 [`BreakpointUpload::upload_chunk`]。
10///
11/// # 与执行器协作的约定
12///
13/// - 若返回的 [`UploadResumeInfo::completed_file_id`] 为 `Some`,执行器认为该次 HTTP 已表示**整文件上传结束**,
14/// 会直接将进度推到文件末尾并结束任务(不再继续分片,也不再调用 [`BreakpointUpload::complete_upload`])。
15/// - [`UploadResumeInfo::next_byte`] 表示服务端建议的下一字节偏移;执行器会将其与本地已传偏移合并后再继续。
16/// - 当某次分片后计算得到的下一偏移已到达或超过 `task.total_size()` 时,执行器会调用
17/// [`BreakpointUpload::complete_upload`](若该分片响应未通过 `completed_file_id` 提前结束)。
18/// - 用户取消上传任务时,执行器对上传方向会调用 [`BreakpointUpload::abort_upload`]。
19#[async_trait]
20pub trait BreakpointUpload: Send + Sync {
21 /// 传输开始前的准备阶段:通常对应「初始化会话 / 查询已传进度」等一次或多次 HTTP 调用。
22 ///
23 /// 在首个分片上传之前由执行器调用一次,用于与服务端对齐续传点或创建分片上传会话。
24 ///
25 /// # 参数
26 ///
27 /// - `client`:执行器持有的 [`reqwest::Client`],应用与分片阶段使用同一客户端配置(代理、TLS、超时等)。
28 /// - `task`:当前上传任务快照,含 URL、方法、请求头、本地路径、文件签名、总分片大小等;请求 URL/方法/头
29 /// 一般应与此保持一致,除非协议明确要求单独的 prepare 端点(此时由实现自行决定 URL)。
30 /// - `local_offset`:本地已确认写入的字节偏移(断点续传时非零);实现可据此与服务端 `nextByte` 等字段对齐。
31 ///
32 /// # 返回值
33 ///
34 /// - `Ok(info)`:见 [`UploadResumeInfo`]。若 `completed_file_id` 为 `Some`,执行器**立即**结束上传成功路径。
35 /// 否则执行器取 `info.next_byte.unwrap_or(0)`,与 `local_offset` 取较大值后再与 `task.total_size()` 取较小值,
36 /// 作为下一分片的起始偏移。
37 /// - `Err`:准备失败,任务进入失败状态(可能触发重试策略,取决于上层配置)。
38 async fn prepare(
39 &self,
40 client: &reqwest::Client,
41 task: &TransferTask,
42 local_offset: u64,
43 ) -> Result<UploadResumeInfo, MeowError>;
44
45 /// 上传单个分片:从本地文件读出的一段字节,由实现封装为 HTTP 请求并发送。
46 ///
47 /// 执行器保证:`chunk` 长度与当前分片一致(最后一片可能更短),`offset` 为该分片在文件中的起始字节偏移,
48 /// 且 `offset + chunk.len() <= task.total_size()`(除边界情况外由执行器保证顺序与范围)。
49 ///
50 /// # 参数
51 ///
52 /// - `client`:同上,用于发送本分片的 HTTP 请求。
53 /// - `task`:当前任务;实现从中读取 URL、方法、头、文件名、总大小、签名等元数据以拼请求体或查询参数。
54 /// - `chunk`:本分片原始字节,只读;不应假设跨调用缓存,每次调用对应独立一次 HTTP。
55 /// - `offset`:本分片在整文件中的起始偏移(从 0 开始),与 `chunk` 内容一一对应。
56 ///
57 /// # 返回值
58 ///
59 /// - `Ok(info)`:若 `completed_file_id` 为 `Some`,执行器认为上传已完成。否则根据 `next_byte` 更新进度:
60 /// 下一偏移为 `info.next_byte.unwrap_or(offset + chunk.len() as u64)`,再与 `task.total_size()` 取最小值。
61 /// 若该下一偏移已达到或超过文件总大小,执行器随后调用 [`BreakpointUpload::complete_upload`]。
62 /// - `Err`:本分片失败,由执行器按重试/失败策略处理。
63 async fn upload_chunk(
64 &self,
65 client: &reqwest::Client,
66 task: &TransferTask,
67 chunk: &[u8],
68 offset: u64,
69 ) -> Result<UploadResumeInfo, MeowError>;
70
71 /// 所有分片字节已按协议上传完毕后,由执行器调用的收尾步骤。
72 ///
73 /// 典型场景:对象存储的 `CompleteMultipartUpload`、合并块列表、通知业务侧落库等。默认实现为空操作(`Ok(())`),
74 /// 适用于服务端在最后一个分片响应中即视为完成、无需额外 HTTP 的协议。
75 ///
76 /// # 参数
77 ///
78 /// - `client`:用于发送完成/提交类请求(若需要)。
79 /// - `task`:当前任务,用于 URL、头及协议所需的业务标识。
80 ///
81 /// # 返回值
82 ///
83 /// - `Ok(())`:收尾成功,执行器将任务标为完成。
84 /// - `Err`:收尾失败,任务失败(可能经重试,取决于上层)。
85 async fn complete_upload(
86 &self,
87 _client: &reqwest::Client,
88 _task: &TransferTask,
89 ) -> Result<(), MeowError> {
90 Ok(())
91 }
92
93 /// 用户取消上传任务时,由执行器调用,用于协议层的清理或中止语义。
94 ///
95 /// 例如:中止分片上传会话、删除未合并的临时对象。默认实现为空操作。仅在上传方向的任务取消路径中调用。
96 ///
97 /// # 参数
98 ///
99 /// - `client`:用于发送中止/删除类请求(若需要)。
100 /// - `task`:被取消的任务,用于定位远端资源或会话 ID。
101 ///
102 /// # 返回值
103 ///
104 /// - `Ok(())`:取消侧协议处理成功(或无可执行操作)。
105 /// - `Err`:中止请求失败;是否影响任务已处于取消状态由上层决定,但实现应尽量返回明确错误信息。
106 async fn abort_upload(
107 &self,
108 _client: &reqwest::Client,
109 _task: &TransferTask,
110 ) -> Result<(), MeowError> {
111 Ok(())
112 }
113}