Skip to main content

rusty_cat/
pounce_task.rs

1use std::fmt;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use reqwest::header::HeaderMap;
6use reqwest::Method;
7
8use crate::direction::Direction;
9use crate::http_breakpoint::{BreakpointDownload, BreakpointDownloadHttpConfig, BreakpointUpload};
10use crate::upload_source::UploadSource;
11
12/// User-facing task input built by upload/download builders.
13///
14/// This type only carries request parameters. Internal runtime state is created
15/// later when the task is enqueued.
16#[derive(Clone)]
17pub struct PounceTask {
18    /// Transfer direction.
19    pub(crate) direction: Direction,
20    /// Display file name.
21    pub(crate) file_name: String,
22    /// Local source/target path.
23    pub(crate) file_path: PathBuf,
24    /// Upload-only source descriptor.
25    pub(crate) upload_source: Option<UploadSource>,
26    /// Total file size in bytes (upload only at build time).
27    pub(crate) total_size: u64,
28    /// Chunk size in bytes.
29    pub(crate) chunk_size: u64,
30    /// Request URL.
31    pub(crate) url: String,
32    /// Request HTTP method.
33    pub(crate) method: Method,
34    /// Base request headers.
35    pub(crate) headers: HeaderMap,
36    /// Download-only signature shown in callbacks.
37    ///
38    /// Upload tasks ignore this value and use internal signature generation.
39    pub(crate) client_file_sign: Option<String>,
40    /// Optional custom upload breakpoint protocol.
41    pub(crate) breakpoint_upload: Option<Arc<dyn BreakpointUpload + Send + Sync>>,
42    /// Optional custom download breakpoint protocol.
43    pub(crate) breakpoint_download: Option<Arc<dyn BreakpointDownload + Send + Sync>>,
44    /// Optional HTTP configuration for breakpoint download.
45    pub(crate) breakpoint_download_http: Option<BreakpointDownloadHttpConfig>,
46    /// Maximum retry count per chunk transfer.
47    ///
48    /// Applies only to chunk transfer stage, not prepare stage.
49    pub(crate) max_chunk_retries: u32,
50    /// Maximum retry count after the first failed upload `prepare` (`BreakpointUpload::prepare`).
51    ///
52    /// Used only for upload direction; download tasks carry the default but do not consult it.
53    pub(crate) max_upload_prepare_retries: u32,
54    /// Maximum number of chunks of THIS file uploaded concurrently (intra-file
55    /// parallel parts). Default `1` keeps the strict-serial path. Values `> 1`
56    /// are only honored for upload protocols that prove out-of-order safety via
57    /// [`crate::http_breakpoint::BreakpointUpload::supports_parallel_parts`];
58    /// any other protocol stays serial. Peak upload memory for a file source is
59    /// `max_parts_in_flight * chunk_size`.
60    pub(crate) max_parts_in_flight: usize,
61}
62
63impl fmt::Debug for PounceTask {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        f.debug_struct("PounceTask")
66            .field("direction", &self.direction)
67            .field("file_name", &self.file_name)
68            .field("file_path", &self.file_path)
69            .field("upload_source", &self.upload_source)
70            .field("total_size", &self.total_size)
71            .field("chunk_size", &self.chunk_size)
72            .field("url", &self.url)
73            .field("method", &self.method)
74            .field("headers", &self.headers)
75            .field("client_file_sign", &self.client_file_sign)
76            .field(
77                "breakpoint_upload",
78                &self
79                    .breakpoint_upload
80                    .as_ref()
81                    .map(|_| "Arc<dyn BreakpointUpload + Send + Sync>"),
82            )
83            .field(
84                "breakpoint_download",
85                &self
86                    .breakpoint_download
87                    .as_ref()
88                    .map(|_| "Arc<dyn BreakpointDownload + Send + Sync>"),
89            )
90            .field("breakpoint_download_http", &self.breakpoint_download_http)
91            .field("max_chunk_retries", &self.max_chunk_retries)
92            .field(
93                "max_upload_prepare_retries",
94                &self.max_upload_prepare_retries,
95            )
96            .field("max_parts_in_flight", &self.max_parts_in_flight)
97            .finish()
98    }
99}
100
101impl PounceTask {
102    /// Default maximum retry count per chunk transfer.
103    pub const DEFAULT_MAX_CHUNK_RETRIES: u32 = 3;
104
105    /// Default maximum retry count after the first failed upload prepare.
106    pub const DEFAULT_MAX_UPLOAD_PREPARE_RETRIES: u32 = 3;
107
108    /// Default number of concurrent in-flight parts per file: `1` (strict
109    /// serial, byte-for-byte the legacy upload path).
110    pub const DEFAULT_MAX_PARTS_IN_FLIGHT: usize = 1;
111
112    /// Normalizes chunk size input.
113    ///
114    /// `0` is converted to `1 MiB`; other values are kept unchanged.
115    pub(crate) fn normalized_chunk_size(chunk_size: u64) -> u64 {
116        if chunk_size == 0 {
117            1024 * 1024
118        } else {
119            chunk_size
120        }
121    }
122
123    /// Normalizes retry count input.
124    ///
125    /// - `0` means "disable retry".
126    /// - Other values are used as-is.
127    pub(crate) fn normalized_max_chunk_retries(max_chunk_retries: u32) -> u32 {
128        max_chunk_retries
129    }
130
131    /// Normalizes upload prepare retry count input (same rules as chunk retries).
132    pub(crate) fn normalized_max_upload_prepare_retries(max_upload_prepare_retries: u32) -> u32 {
133        max_upload_prepare_retries
134    }
135
136    /// Normalizes the concurrent in-flight parts count.
137    ///
138    /// `0` collapses to `1` (serial) so a misconfigured value can never disable
139    /// progress; other values are used as-is.
140    pub(crate) fn normalized_max_parts_in_flight(max_parts_in_flight: usize) -> usize {
141        max_parts_in_flight.max(1)
142    }
143
144    /// Checks whether required task fields are missing/invalid.
145    ///
146    /// For upload, `total_size` must be greater than `0`.
147    pub(crate) fn is_empty(&self) -> bool {
148        self.file_name.is_empty()
149            || self.url.is_empty()
150            || match self.direction {
151                Direction::Upload => self.total_size == 0 || self.upload_source.is_none(),
152                Direction::Download => self.file_path.as_os_str().is_empty(),
153            }
154    }
155}