1use std::future::{Future, IntoFuture};
2use std::marker::PhantomData;
3use std::pin::Pin;
4use std::task::{ready, Context, Poll};
5
6use bytes::Bytes;
7use futures_util::TryStream;
8
9use crate::request::files::multipart::{
10 AddMultipartUploadFile, ContentRange, CreateMultipartUploadSession,
11};
12use crate::types::files::multipart::{UploadId, UploadPart, UploadSession};
13use crate::types::files::File;
14use crate::types::id::{GameId, ModId};
15use crate::util::{DataError, DataFromRequest, DataFuture};
16use crate::Client;
17
18use super::Error;
19
20pub struct MultipartUploader<'a, State> {
21 state: State,
22 phantom: PhantomData<fn(&'a State) -> State>,
23}
24
25impl<'a> MultipartUploader<'a, ()> {
26 pub(crate) const fn init(
27 http: &'a Client,
28 game_id: GameId,
29 mod_id: ModId,
30 filename: &'a str,
31 ) -> MultipartUploader<'a, Init<'a>> {
32 MultipartUploader {
33 state: Init {
34 http,
35 game_id,
36 mod_id,
37 create_session: http.create_multipart_upload_session(game_id, mod_id, filename),
38 },
39 phantom: PhantomData,
40 }
41 }
42
43 pub(crate) const fn started(
44 http: &'a Client,
45 game_id: GameId,
46 mod_id: ModId,
47 upload_id: UploadId,
48 ) -> MultipartUploader<'a, Started<'a>> {
49 MultipartUploader {
50 state: Started {
51 http,
52 game_id,
53 mod_id,
54 upload_id,
55 },
56 phantom: PhantomData,
57 }
58 }
59}
60
61pub struct Init<'a> {
62 http: &'a Client,
63 game_id: GameId,
64 mod_id: ModId,
65 create_session: CreateMultipartUploadSession<'a>,
66}
67
68impl<'a> MultipartUploader<'a, Init<'a>> {
69 pub const fn nonce(mut self, nonce: &'a str) -> Self {
74 self.state.create_session = self.state.create_session.nonce(nonce);
75 self
76 }
77}
78
79impl<'a> IntoFuture for MultipartUploader<'a, Init<'a>> {
80 type Output = <MultipartUploaderInitFuture<'a> as Future>::Output;
81 type IntoFuture = MultipartUploaderInitFuture<'a>;
82
83 fn into_future(self) -> Self::IntoFuture {
84 let Init {
85 http,
86 game_id,
87 mod_id,
88 create_session,
89 } = self.state;
90
91 MultipartUploaderInitFuture {
92 future: create_session.data(),
93 state: Some((http, game_id, mod_id)),
94 }
95 }
96}
97
98pin_project_lite::pin_project! {
99 pub struct MultipartUploaderInitFuture<'a> {
100 #[pin]
101 future: DataFuture<UploadSession>,
102 state: Option<(&'a Client, GameId, ModId)>,
103 }
104}
105
106impl<'a> Future for MultipartUploaderInitFuture<'a> {
107 type Output = Result<MultipartUploader<'a, Started<'a>>, Error>;
108
109 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
110 let mut this = self.project();
111
112 let result = match ready!(this.future.as_mut().poll(cx)) {
113 Ok(session) => {
114 let (http, game_id, mod_id) = this.state.take().expect("Polled after completion");
115 Ok(MultipartUploader {
116 state: Started {
117 http,
118 game_id,
119 mod_id,
120 upload_id: session.id,
121 },
122 phantom: PhantomData,
123 })
124 }
125 Err(err) => Err(match err {
126 DataError::Request(err) => Error::request(err),
127 DataError::Body(err) => Error::body(err),
128 }),
129 };
130
131 Poll::Ready(result)
132 }
133}
134
135pub struct Started<'a> {
136 http: &'a Client,
137 game_id: GameId,
138 mod_id: ModId,
139 upload_id: UploadId,
140}
141
142impl<'a> MultipartUploader<'a, Started<'a>> {
143 pub const fn id(&self) -> UploadId {
144 self.state.upload_id
145 }
146
147 pub async fn get_parts(&self) -> Result<Vec<UploadPart>, Error> {
149 let Started {
150 http,
151 game_id,
152 mod_id,
153 upload_id,
154 } = self.state;
155
156 match http
157 .get_multipart_upload_parts(game_id, mod_id, upload_id)
158 .data()
159 .await
160 {
161 Ok(parts) => Ok(parts.data),
162 Err(err) => Err(match err {
163 DataError::Request(err) => Error::request(err),
164 DataError::Body(err) => Error::body(err),
165 }),
166 }
167 }
168
169 pub async fn add_part<S>(&self, range: ContentRange, stream: S) -> Result<UploadPart, Error>
171 where
172 S: TryStream + Send + 'static,
173 S::Ok: Into<Bytes>,
174 S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
175 {
176 let Started {
177 http,
178 game_id,
179 mod_id,
180 upload_id,
181 } = self.state;
182
183 match http
184 .add_multipart_upload_part(game_id, mod_id, upload_id, range, stream)
185 .data()
186 .await
187 {
188 Ok(part) => Ok(part),
189 Err(err) => Err(match err {
190 DataError::Request(err) => Error::request(err),
191 DataError::Body(err) => Error::body(err),
192 }),
193 }
194 }
195
196 pub async fn complete(self) -> Result<MultipartUploader<'a, Completed<'a>>, Error> {
199 let Started {
200 http,
201 game_id,
202 mod_id,
203 upload_id,
204 } = self.state;
205
206 let upload_id = match http
207 .complete_multipart_upload_session(game_id, mod_id, upload_id)
208 .data()
209 .await
210 {
211 Ok(UploadSession { id, .. }) => id,
212 Err(err) => {
213 return Err(match err {
214 DataError::Request(err) => Error::request(err),
215 DataError::Body(err) => Error::body(err),
216 })
217 }
218 };
219
220 let add_file = http.add_multipart_upload_file(game_id, mod_id, upload_id);
221 Ok(MultipartUploader {
222 state: Completed { add_file },
223 phantom: PhantomData,
224 })
225 }
226
227 pub async fn abort(self) -> Result<(), Error> {
229 let Started {
230 http,
231 game_id,
232 mod_id,
233 upload_id,
234 } = self.state;
235
236 if let Err(err) = http
237 .delete_multipart_upload_session(game_id, mod_id, upload_id)
238 .await
239 {
240 return Err(Error::request(err));
241 }
242
243 Ok(())
244 }
245}
246
247pub struct Completed<'a> {
248 add_file: AddMultipartUploadFile<'a>,
249}
250
251impl<'a> MultipartUploader<'a, Completed<'a>> {
252 pub const fn active(mut self, active: bool) -> Self {
253 self.state.add_file = self.state.add_file.active(active);
254 self
255 }
256
257 pub const fn changelog(mut self, changelog: &'a str) -> Self {
258 self.state.add_file = self.state.add_file.changelog(changelog);
259 self
260 }
261
262 pub const fn filehash(mut self, filehash: &'a str) -> Self {
263 self.state.add_file = self.state.add_file.filehash(filehash);
264 self
265 }
266
267 pub const fn metadata_blob(mut self, metadata: &'a str) -> Self {
268 self.state.add_file = self.state.add_file.metadata_blob(metadata);
269 self
270 }
271
272 pub const fn version(mut self, version: &'a str) -> Self {
273 self.state.add_file = self.state.add_file.version(version);
274 self
275 }
276}
277
278impl IntoFuture for MultipartUploader<'_, Completed<'_>> {
279 type Output = <MultipartUploaderCompleteFuture as Future>::Output;
280 type IntoFuture = MultipartUploaderCompleteFuture;
281
282 fn into_future(self) -> Self::IntoFuture {
283 let Completed { add_file } = self.state;
284
285 MultipartUploaderCompleteFuture {
286 future: add_file.data(),
287 }
288 }
289}
290
291pin_project_lite::pin_project! {
292 pub struct MultipartUploaderCompleteFuture {
293 #[pin]
294 future: DataFuture<File>,
295 }
296}
297
298impl Future for MultipartUploaderCompleteFuture {
299 type Output = Result<File, Error>;
300
301 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
302 let mut this = self.project();
303
304 let result = match ready!(this.future.as_mut().poll(cx)) {
305 Ok(file) => Ok(file),
306 Err(err) => Err(match err {
307 DataError::Request(err) => Error::request(err),
308 DataError::Body(err) => Error::body(err),
309 }),
310 };
311
312 Poll::Ready(result)
313 }
314}