1use crate::{StorageResult, StorageError};
4use bytes::Bytes;
5use futures::Stream;
6use std::pin::Pin;
7use std::task::{Context, Poll};
8
9pub struct UploadStream<S> {
11 inner: S,
12 bytes_read: u64,
13 max_size: Option<u64>,
14}
15
16impl<S> UploadStream<S>
17where
18 S: Stream<Item = Result<Bytes, std::io::Error>>,
19{
20 pub fn new(stream: S, max_size: Option<u64>) -> Self {
22 Self {
23 inner: stream,
24 bytes_read: 0,
25 max_size,
26 }
27 }
28
29 pub fn bytes_read(&self) -> u64 {
31 self.bytes_read
32 }
33}
34
35impl<S> Stream for UploadStream<S>
36where
37 S: Stream<Item = Result<Bytes, std::io::Error>> + Unpin,
38{
39 type Item = Result<Bytes, std::io::Error>;
40
41 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
42 match Pin::new(&mut self.inner).poll_next(cx) {
43 Poll::Ready(Some(Ok(chunk))) => {
44 self.bytes_read += chunk.len() as u64;
45
46 if let Some(max_size) = self.max_size {
48 if self.bytes_read > max_size {
49 return Poll::Ready(Some(Err(std::io::Error::new(
50 std::io::ErrorKind::InvalidData,
51 format!("Upload size {} exceeds maximum {}", self.bytes_read, max_size),
52 ))));
53 }
54 }
55
56 Poll::Ready(Some(Ok(chunk)))
57 }
58 other => other,
59 }
60 }
61}
62
63pub struct MultipartUpload {
65 upload_id: String,
66 parts: Vec<UploadPart>,
67 current_part: u32,
68}
69
70#[derive(Debug, Clone)]
72pub struct UploadPart {
73 pub part_number: u32,
74 pub etag: String,
75 pub size: u64,
76}
77
78impl MultipartUpload {
79 pub fn new(upload_id: String) -> Self {
81 Self {
82 upload_id,
83 parts: Vec::new(),
84 current_part: 1,
85 }
86 }
87
88 pub fn upload_id(&self) -> &str {
90 &self.upload_id
91 }
92
93 pub fn add_part(&mut self, etag: String, size: u64) {
95 self.parts.push(UploadPart {
96 part_number: self.current_part,
97 etag,
98 size,
99 });
100 self.current_part += 1;
101 }
102
103 pub fn parts(&self) -> &[UploadPart] {
105 &self.parts
106 }
107
108 pub fn total_size(&self) -> u64 {
110 self.parts.iter().map(|p| p.size).sum()
111 }
112
113 pub fn is_complete(&self) -> bool {
115 !self.parts.is_empty()
116 }
117}
118
119pub type ProgressCallback = Box<dyn Fn(u64, u64) + Send + Sync>;
121
122pub struct UploadConfig {
124 pub chunk_size: usize,
126
127 pub report_progress: bool,
129
130 pub progress_callback: Option<Box<dyn Fn(u64, u64) + Send + Sync>>,
132
133 pub max_concurrent_parts: usize,
135
136 pub min_part_size: u64,
138}
139
140impl std::fmt::Debug for UploadConfig {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 f.debug_struct("UploadConfig")
143 .field("chunk_size", &self.chunk_size)
144 .field("report_progress", &self.report_progress)
145 .field("progress_callback", &self.progress_callback.as_ref().map(|_| "Some(callback)"))
146 .field("max_concurrent_parts", &self.max_concurrent_parts)
147 .field("min_part_size", &self.min_part_size)
148 .finish()
149 }
150}
151
152impl Default for UploadConfig {
153 fn default() -> Self {
154 Self {
155 chunk_size: 8 * 1024 * 1024, report_progress: false,
157 progress_callback: None,
158 max_concurrent_parts: 4,
159 min_part_size: 5 * 1024 * 1024, }
161 }
162}
163
164impl UploadConfig {
165 pub fn new() -> Self {
167 Self::default()
168 }
169
170 pub fn with_chunk_size(mut self, size: usize) -> Self {
172 self.chunk_size = size;
173 self
174 }
175
176 pub fn with_progress<F>(mut self, callback: F) -> Self
178 where
179 F: Fn(u64, u64) + Send + Sync + 'static,
180 {
181 self.report_progress = true;
182 self.progress_callback = Some(Box::new(callback));
183 self
184 }
185
186 pub fn with_max_concurrent_parts(mut self, max: usize) -> Self {
188 self.max_concurrent_parts = max;
189 self
190 }
191
192 pub fn with_min_part_size(mut self, size: u64) -> Self {
194 self.min_part_size = size;
195 self
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use futures::{stream, StreamExt};
203 use std::io;
204
205 #[tokio::test]
206 async fn test_upload_stream() {
207 let data = vec![
208 Ok(Bytes::from("hello")),
209 Ok(Bytes::from(" ")),
210 Ok(Bytes::from("world")),
211 ];
212
213 let stream = stream::iter(data);
214 let mut upload_stream = UploadStream::new(stream, Some(20));
215
216 let mut result = Vec::new();
217 while let Some(chunk) = upload_stream.next().await {
218 result.push(chunk.unwrap());
219 }
220
221 let combined: Bytes = result.into_iter().collect::<Vec<_>>().concat().into();
222 assert_eq!(combined, Bytes::from("hello world"));
223 assert_eq!(upload_stream.bytes_read(), 11);
224 }
225
226 #[tokio::test]
227 async fn test_upload_stream_size_limit() {
228 let data = vec![
229 Ok(Bytes::from("hello")),
230 Ok(Bytes::from(" world")),
231 ];
232
233 let stream = stream::iter(data);
234 let mut upload_stream = UploadStream::new(stream, Some(5)); let chunk1 = upload_stream.next().await.unwrap().unwrap();
238 assert_eq!(chunk1, Bytes::from("hello"));
239
240 let chunk2 = upload_stream.next().await.unwrap();
242 assert!(chunk2.is_err());
243 assert_eq!(chunk2.unwrap_err().kind(), io::ErrorKind::InvalidData);
244 }
245
246 #[test]
247 fn test_multipart_upload() {
248 let mut upload = MultipartUpload::new("test-upload-123".to_string());
249
250 assert_eq!(upload.upload_id(), "test-upload-123");
251 assert!(!upload.is_complete());
252 assert_eq!(upload.total_size(), 0);
253
254 upload.add_part("etag1".to_string(), 1000);
255 upload.add_part("etag2".to_string(), 2000);
256
257 assert!(upload.is_complete());
258 assert_eq!(upload.total_size(), 3000);
259 assert_eq!(upload.parts().len(), 2);
260
261 let part1 = &upload.parts()[0];
262 assert_eq!(part1.part_number, 1);
263 assert_eq!(part1.etag, "etag1");
264 assert_eq!(part1.size, 1000);
265 }
266
267 #[test]
268 fn test_upload_config() {
269 let config = UploadConfig::new()
270 .with_chunk_size(16 * 1024 * 1024)
271 .with_max_concurrent_parts(8)
272 .with_min_part_size(10 * 1024 * 1024);
273
274 assert_eq!(config.chunk_size, 16 * 1024 * 1024);
275 assert_eq!(config.max_concurrent_parts, 8);
276 assert_eq!(config.min_part_size, 10 * 1024 * 1024);
277 assert!(!config.report_progress);
278 }
279}