rusty_cat/transfer_executor_trait.rs
1use async_trait::async_trait;
2
3use crate::chunk_outcome::ChunkOutcome;
4use crate::error::MeowError;
5use crate::prepare_outcome::PrepareOutcome;
6use crate::transfer_task::TransferTask;
7
8/// Low-level transfer executor abstraction used by scheduler/runtime.
9///
10/// Most users do not implement this trait directly unless they are building a
11/// custom transport backend.
12///
13/// # Examples
14///
15/// ```no_run
16/// use async_trait::async_trait;
17/// use rusty_cat::api::{ChunkOutcome, MeowError, PrepareOutcome, TransferTask, TransferTrait};
18///
19/// struct NoopExecutor;
20///
21/// #[async_trait]
22/// impl TransferTrait for NoopExecutor {
23/// async fn prepare(
24/// &self,
25/// _task: &TransferTask,
26/// local_offset: u64,
27/// ) -> Result<PrepareOutcome, MeowError> {
28/// Ok(PrepareOutcome { next_offset: local_offset, total_size: local_offset })
29/// }
30///
31/// async fn transfer_chunk(
32/// &self,
33/// _task: &TransferTask,
34/// offset: u64,
35/// _chunk_size: u64,
36/// remote_total_size: u64,
37/// ) -> Result<ChunkOutcome, MeowError> {
38/// Ok(ChunkOutcome {
39/// next_offset: offset,
40/// total_size: remote_total_size,
41/// done: true,
42/// completion_payload: None,
43/// })
44/// }
45/// }
46/// ```
47#[async_trait]
48pub trait TransferTrait: Send + Sync {
49 /// Prepares transfer state and computes the next offset to run.
50 ///
51 /// # Parameters
52 ///
53 /// - `task`: Immutable task snapshot.
54 /// - `local_offset`: Current local persisted offset, in bytes.
55 ///
56 /// # Returns
57 ///
58 /// Returns [`PrepareOutcome`] containing next offset and total size.
59 ///
60 /// # Errors
61 ///
62 /// Return [`MeowError`] when checkpoint probing, local/remote validation,
63 /// or protocol initialization fails.
64 ///
65 /// # Examples
66 ///
67 /// ```no_run
68 /// use rusty_cat::api::TransferTask;
69 ///
70 /// fn inspect_prepare_input(task: &TransferTask, local_offset: u64) {
71 /// let _ = (task.url(), local_offset);
72 /// }
73 /// ```
74 async fn prepare(
75 &self,
76 task: &TransferTask,
77 local_offset: u64,
78 ) -> Result<PrepareOutcome, MeowError>;
79
80 /// Transfers one chunk from the given offset.
81 ///
82 /// # Parameters
83 ///
84 /// - `offset`: Start byte offset for this chunk.
85 /// - `chunk_size`: Desired chunk size in bytes (`>= 1` recommended).
86 /// - `remote_total_size`: For download, use `prepare.total_size`; for
87 /// upload, usually equals `task.total_size()`.
88 ///
89 /// # Errors
90 ///
91 /// Return [`MeowError`] when chunk transfer fails, range validation fails,
92 /// or local file I/O fails.
93 ///
94 /// # Examples
95 ///
96 /// ```no_run
97 /// use rusty_cat::api::TransferTask;
98 ///
99 /// fn inspect_chunk_input(task: &TransferTask, offset: u64, chunk_size: u64, remote_total_size: u64) {
100 /// let _ = (task.file_name(), offset, chunk_size, remote_total_size);
101 /// }
102 /// ```
103 async fn transfer_chunk(
104 &self,
105 task: &TransferTask,
106 offset: u64,
107 chunk_size: u64,
108 remote_total_size: u64,
109 ) -> Result<ChunkOutcome, MeowError>;
110
111 /// Handles protocol-specific cancel semantics.
112 ///
113 /// Default implementation is a no-op.
114 ///
115 /// # Errors
116 ///
117 /// Implementations should return [`MeowError`] if remote abort/cancel
118 /// actions fail.
119 async fn cancel(&self, _task: &TransferTask) -> Result<(), MeowError> {
120 Ok(())
121 }
122
123 /// Whether this executor may transfer chunks of `task` concurrently and out
124 /// of order (intra-file parallel parts).
125 ///
126 /// Default `false` keeps every executor on the strict-serial path. When this
127 /// returns `true`, the scheduler may dispatch up to `max_parts_in_flight`
128 /// chunks of the same file at once and finalize the upload via [`Self::complete`]
129 /// instead of letting any single chunk finalize inline.
130 ///
131 /// **Contract:** an executor that returns `true` here MUST also override
132 /// [`Self::transfer_chunk_part`] (to upload a chunk WITHOUT finalizing) and
133 /// [`Self::complete`] (to finalize exactly once). Leaving those at their
134 /// defaults while returning `true` would finalize the upload from whichever
135 /// chunk reaches the end first, corrupting out-of-order uploads.
136 fn supports_parallel_parts(&self, _task: &TransferTask) -> bool {
137 false
138 }
139
140 /// Transfers one chunk WITHOUT finalizing the transfer.
141 ///
142 /// Used by the parallel-parts path: completion is hoisted to a single
143 /// [`Self::complete`] call made by the scheduler after every part has been
144 /// uploaded as a contiguous prefix, so individual chunks must not finalize.
145 ///
146 /// The default delegates to [`Self::transfer_chunk`], which is correct for
147 /// executors that do not opt into parallel parts (they never take this path);
148 /// executors returning `true` from [`Self::supports_parallel_parts`] MUST
149 /// override this to skip finalization.
150 ///
151 /// # Errors
152 ///
153 /// Same as [`Self::transfer_chunk`].
154 async fn transfer_chunk_part(
155 &self,
156 task: &TransferTask,
157 offset: u64,
158 chunk_size: u64,
159 remote_total_size: u64,
160 ) -> Result<ChunkOutcome, MeowError> {
161 self.transfer_chunk(task, offset, chunk_size, remote_total_size)
162 .await
163 }
164
165 /// Finalizes a transfer after all parts have been uploaded (parallel path).
166 ///
167 /// Called exactly once by the scheduler once the whole file has arrived as a
168 /// contiguous prefix and every in-flight part has joined. The default is a
169 /// no-op (`Ok(None)`); the bundled executor delegates to the upload
170 /// protocol's completion step.
171 ///
172 /// # Errors
173 ///
174 /// Return [`MeowError`] when the final commit/merge call fails.
175 async fn complete(&self, _task: &TransferTask) -> Result<Option<String>, MeowError> {
176 Ok(None)
177 }
178}