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}