backblaze_b2_client/tasks/upload/
options.rs

1use crate::{
2    definitions::{
3        bodies::B2StartLargeFileUploadBody,
4        headers::{B2UploadFileHeaders, B2UploadPartHeaders},
5        shared::{B2BucketFileRetention, B2FileLegalHold, B2ServerSideEncryption},
6    },
7    throttle::Throttle,
8    util::{InvalidValue, IsValid, RetryStrategy, SizeUnit},
9};
10
11/// File upload options
12#[derive(Debug)]
13pub struct FileUploadOptions {
14    /// Cut off point for the file to count as a big file, from 5 Mib - 5 Gib.
15    /// <br> Default is 200 Mib.
16    pub large_file_cutoff: u64,
17    /// The large file load strategy, refer to [ConstantLargeFileLoadStrategy] to find how they work.
18    /// <br> Defaults to LargeFileLoadStrategy::Dynamic([DefaultLargeFileLoadStrategy])
19    pub file_load_strategy: LargeFileLoadStrategy,
20    /// Upload speed throttle, can be used as
21    /// ```rust
22    /// // Translates to a MiBPS upload speed limit
23    /// let throttle = Throttle::per_second(SizeUnit::MEBIBYTE * 5);
24    /// ```
25    /// <br> Default is None.
26    pub speed_throttle: Option<Throttle<u64>>,
27    /// Retry strategy on request failure.
28    /// <br> Defaults to RetryStrategy::Dynamic([crate::util::DefaultRetryStrategy]).
29    pub retry_strategy: RetryStrategy,
30    /// The extra file upload options B2 provides
31    /// <br> Check default for [B2FileUploadSettings]
32    pub options: B2FileUploadSettings,
33}
34
35impl Default for FileUploadOptions {
36    fn default() -> Self {
37        Self {
38            large_file_cutoff: SizeUnit::MEBIBYTE * 200,
39            file_load_strategy: Default::default(),
40            speed_throttle: None,
41            retry_strategy: Default::default(),
42            options: Default::default(),
43        }
44    }
45}
46
47impl IsValid for FileUploadOptions {
48    fn is_valid(&self) -> Result<(), InvalidValue> {
49        if self.large_file_cutoff < SizeUnit::MEBIBYTE * 5
50            && self.large_file_cutoff > SizeUnit::GIBIBYTE * 5
51        {
52            return Err(InvalidValue {
53                object_name: "FileUploadOptions".into(),
54                value_name: "large_file_cutoff".into(),
55                value_as_string: SizeUnit::from(self.large_file_cutoff as f64).to_string(),
56                expected: "5 MiB - 5 GiB".into(),
57            });
58        }
59
60        Ok(())
61    }
62}
63
64/// The large file load strategy, refer to [ConstantLargeFileLoadStrategy] to find how they work.
65#[derive(Debug)]
66pub enum LargeFileLoadStrategy {
67    Constant(ConstantLargeFileLoadStrategy),
68    Dynamic(Box<dyn DynamicLargeFileLoadStrategy + Send + Sync>),
69}
70
71impl Default for LargeFileLoadStrategy {
72    fn default() -> Self {
73        Self::Dynamic(Box::new(DefaultLargeFileLoadStrategy))
74    }
75}
76
77/// Dictates how large file parts are loaded
78/// the approximate total bytes of the file that would be loaded at once will equal `file_size / chunk_size` rounded up to biggest number.
79/// part_size must be smaller than the calculated number.
80///
81/// <br> For example, if we take the default values for bytes and chunk_size of `5 Mib` and `3`, and we're upload a `500 Mib` file
82/// the total bytes of the file that would be loaded at once will equal `500 / 3` which is ~166 mibs.
83#[derive(Debug, Clone)]
84pub struct ConstantLargeFileLoadStrategy {
85    /// size of the file part, from 5 Mib - 5 Gib.
86    /// <br> Default 5 Mib.
87    pub part_size: u64,
88    /// How many parts are handled per task. must be at least 1.
89    /// <br> Default 3.
90    pub chunk_size: u16,
91}
92
93impl IsValid for ConstantLargeFileLoadStrategy {
94    fn is_valid(&self) -> Result<(), InvalidValue> {
95        if self.chunk_size < 1 {
96            return Err(InvalidValue {
97                object_name: "ConstantLargeFileLoadStrategy".into(),
98                value_name: "chunk_size".into(),
99                value_as_string: self.chunk_size.to_string(),
100                expected: "at least 1".into(),
101            });
102        }
103
104        if self.part_size < SizeUnit::MEBIBYTE * 5 && self.part_size > SizeUnit::GIBIBYTE * 5 {
105            return Err(InvalidValue {
106                object_name: "ConstantLargeFileLoadStrategy".into(),
107                value_name: "part_size".into(),
108                value_as_string: SizeUnit::from(self.part_size as f64).to_string(),
109                expected: "5 MiB - 5 GiB".into(),
110            });
111        }
112
113        Ok(())
114    }
115}
116
117impl Default for ConstantLargeFileLoadStrategy {
118    fn default() -> Self {
119        Self {
120            part_size: SizeUnit::MEBIBYTE * 5,
121            chunk_size: 3,
122        }
123    }
124}
125
126/// A dynamic file load strategy, refer to [ConstantLargeFileLoadStrategy] to find how they work.
127pub trait DynamicLargeFileLoadStrategy: std::fmt::Debug {
128    fn get_load_strategy(&self, file_size: u64) -> ConstantLargeFileLoadStrategy;
129}
130
131#[derive(Debug)]
132pub struct DefaultLargeFileLoadStrategy;
133
134impl DynamicLargeFileLoadStrategy for DefaultLargeFileLoadStrategy {
135    fn get_load_strategy(&self, file_size: u64) -> ConstantLargeFileLoadStrategy {
136        // tries to limit number of parts to 600
137        let chunk_size = ((file_size / (SizeUnit::MEBIBYTE * 5)) / 200).max(3);
138        let chunk_size = chunk_size.min(u16::MAX as u64) as u16;
139
140        ConstantLargeFileLoadStrategy {
141            part_size: SizeUnit::MEBIBYTE * 5,
142            chunk_size,
143        }
144    }
145}
146
147/// File upload settings, check [file upload](crate::simple_client::B2SimpleClient::upload_file) to learn mode
148#[derive(Clone, Debug)]
149pub struct B2FileUploadSettings {
150    /// Default to `b2/x-auto`
151    pub content_type: String,
152    pub src_last_modified_millis: Option<u64>,
153    pub b2_content_disposition: Option<String>,
154    pub b2_content_language: Option<String>,
155    pub b2_expires: Option<String>,
156    pub b2_cache_control: Option<String>,
157    pub b2_content_encoding: Option<String>,
158    pub custom_upload_timestamp: Option<u64>,
159    pub legal_hold: Option<B2FileLegalHold>,
160    pub file_retention: Option<B2BucketFileRetention>,
161    pub server_side_encryption: Option<B2ServerSideEncryption>,
162}
163
164impl B2FileUploadSettings {
165    pub(super) fn apply_file_upload(self, mut u: B2UploadFileHeaders) -> B2UploadFileHeaders {
166        u.content_type = self.content_type;
167        u.src_last_modified_millis = self.src_last_modified_millis;
168        u.b2_content_disposition = self.b2_content_disposition;
169        u.b2_content_language = self.b2_content_language;
170        u.b2_expires = self.b2_expires;
171        u.b2_cache_control = self.b2_cache_control;
172        u.b2_content_encoding = self.b2_content_encoding;
173        u.custom_upload_timestamp = self.custom_upload_timestamp;
174        u.legal_hold = self.legal_hold;
175
176        if let Some(retention) = self.file_retention {
177            u.retention_mode = retention.mode;
178            u.retention_retain_until_timestamp = retention.retain_until_timestamp;
179        }
180
181        u.server_side_encryption = self.server_side_encryption;
182
183        u
184    }
185
186    pub(super) fn apply_large_file_upload(
187        self,
188        mut u: B2StartLargeFileUploadBody,
189    ) -> B2StartLargeFileUploadBody {
190        u.content_type = self.content_type;
191        u.custom_upload_timestamp = self.custom_upload_timestamp;
192        u.legal_hold = self.legal_hold;
193        u.file_retention = self.file_retention;
194        u.server_side_encryption = self.server_side_encryption;
195
196        if let Some(ref mut info) = u.file_info {
197            if let Some(v) = self.src_last_modified_millis {
198                info.insert("src_last_modified_millis".into(), v.to_string());
199            }
200
201            if let Some(v) = self.b2_content_disposition {
202                info.insert("b2-content-disposition".into(), v);
203            }
204
205            if let Some(v) = self.b2_content_language {
206                info.insert("b2-content-language".into(), v);
207            }
208
209            if let Some(v) = self.b2_expires {
210                info.insert("b2-expires".into(), v);
211            }
212
213            if let Some(v) = self.b2_cache_control {
214                info.insert("b2-cache-control".into(), v);
215            }
216
217            if let Some(v) = self.b2_content_encoding {
218                info.insert("b2-content-encoding".into(), v);
219            }
220        }
221
222        u
223    }
224
225    pub(super) fn apply_file_part_upload(self, mut u: B2UploadPartHeaders) -> B2UploadPartHeaders {
226        if let Some(enc) = self.server_side_encryption {
227            use B2ServerSideEncryption::*;
228
229            match enc {
230                SseB2 { algorithm } => {
231                    u.server_side_encryption_customer_algorithm = Some(algorithm);
232                }
233                SseC {
234                    algorithm,
235                    customer_key,
236                    customer_key_md5,
237                } => {
238                    u.server_side_encryption_customer_algorithm = Some(algorithm);
239                    u.server_side_encryption_customer_key = Some(customer_key);
240                    u.server_side_encryption_customer_key_md5 = Some(customer_key_md5);
241                }
242                Disabled => {}
243            }
244        }
245
246        u
247    }
248}
249
250impl Default for B2FileUploadSettings {
251    fn default() -> Self {
252        Self {
253            content_type: "b2/x-auto".into(),
254            src_last_modified_millis: None,
255            b2_content_disposition: None,
256            b2_content_language: None,
257            b2_expires: None,
258            b2_cache_control: None,
259            b2_content_encoding: None,
260            custom_upload_timestamp: None,
261            legal_hold: None,
262            file_retention: None,
263            server_side_encryption: None,
264        }
265    }
266}