Skip to main content

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}