misskey_util/builder/
drive.rs

1use std::path::{Path, PathBuf};
2
3use crate::pager::{BackwardPager, BoxPager, PagerStream};
4use crate::Error;
5
6#[cfg(feature = "12-48-0")]
7use futures::stream::TryStreamExt;
8use mime::Mime;
9use misskey_api::model::drive::{DriveFile, DriveFolder};
10#[cfg(feature = "12-48-0")]
11use misskey_api::streaming::channel;
12use misskey_api::{endpoint, EntityRef};
13#[cfg(feature = "12-48-0")]
14use misskey_core::streaming::StreamingClient;
15use misskey_core::{Client, UploadFileClient};
16#[cfg(feature = "12-48-0")]
17use ulid_crate::Ulid;
18use url::Url;
19
20/// Builder for the [`build_file_from_url`][`crate::ClientExt::build_file_from_url`] method.
21pub struct DriveFileUrlBuilder<C> {
22    client: C,
23    #[cfg(feature = "12-48-0")]
24    marker: String,
25    request: endpoint::drive::files::upload_from_url::Request,
26}
27
28impl<C> DriveFileUrlBuilder<C> {
29    /// Creates a builder with the client and URL of the upload source.
30    pub fn with_url(client: C, url: Url) -> Self {
31        #[cfg(feature = "12-48-0")]
32        let marker = Ulid::new().to_string();
33        let request = endpoint::drive::files::upload_from_url::Request {
34            url,
35            folder_id: None,
36            is_sensitive: Some(false),
37            force: Some(false),
38            #[cfg(feature = "12-48-0")]
39            comment: None,
40            #[cfg(feature = "12-48-0")]
41            marker: Some(marker.clone()),
42        };
43        DriveFileUrlBuilder {
44            client,
45            request,
46            #[cfg(feature = "12-48-0")]
47            marker,
48        }
49    }
50
51    /// Gets the request object for reuse.
52    pub fn as_request(&self) -> &endpoint::drive::files::upload_from_url::Request {
53        &self.request
54    }
55
56    /// Sets the parent folder of the file.
57    pub fn folder(&mut self, folder: impl EntityRef<DriveFolder>) -> &mut Self {
58        self.request.folder_id.replace(folder.entity_ref());
59        self
60    }
61
62    /// Sets the comment for the file.
63    #[cfg(feature = "12-48-0")]
64    #[cfg_attr(docsrs, doc(cfg(feature = "12-48-0")))]
65    pub fn comment(&mut self, comment: impl Into<String>) -> &mut Self {
66        self.request.comment.replace(comment.into());
67        self
68    }
69
70    /// Sets whether the file contains NSFW content.
71    pub fn sensitive(&mut self, sensitive: bool) -> &mut Self {
72        self.request.is_sensitive = Some(sensitive);
73        self
74    }
75
76    /// Sets whether or not to upload the file again even if the same content has already been
77    /// uploaded.
78    pub fn use_existing_if_uploaded(&mut self, use_existing_if_uploaded: bool) -> &mut Self {
79        self.request.force = Some(!use_existing_if_uploaded);
80        self
81    }
82}
83
84impl<C: Client> DriveFileUrlBuilder<C> {
85    /// Uploads the file.
86    ///
87    /// The difference between [`upload_`][alt] and this method is that the former
88    /// can get the [`DriveFile`][drive_file] of the uploaded file, while the latter cannot.
89    /// If you want to obtain the [`DriveFile`] of an uploaded file in v12.48.0 or later, you can
90    /// use [`upload_and_wait`][wait] or download the file once on the client side
91    /// and the use [`UploadFileClientExt::upload_file`][upload_file] to upload it.
92    ///
93    /// [alt]: DriveFileUrlBuilder::upload_
94    /// [drive_file]: misskey_api::model::drive::DriveFile
95    /// [wait]: DriveFileUrlBuilder::upload_and_wait
96    /// [upload_file]: crate::UploadFileClientExt::upload_file
97    #[cfg(feature = "12-48-0")]
98    #[cfg_attr(docsrs, doc(cfg(feature = "12-48-0")))]
99    pub async fn upload(&self) -> Result<(), Error<C::Error>> {
100        self.client
101            .request(&self.request)
102            .await
103            .map_err(Error::Client)?
104            .into_result()?;
105        Ok(())
106    }
107
108    /// Uploads the file.
109    ///
110    /// See [`upload`][alt] for the difference between them.
111    ///
112    /// [alt]: DriveFileUrlBuilder::upload
113    #[cfg(any(docsrs, not(feature = "12-48-0")))]
114    #[cfg_attr(docsrs, doc(cfg(not(feature = "12-48-0"))))]
115    pub async fn upload_(&self) -> Result<DriveFile, Error<C::Error>> {
116        let file = self
117            .client
118            .request(&self.request)
119            .await
120            .map_err(Error::Client)?
121            .into_result()?;
122        Ok(file)
123    }
124}
125
126#[cfg(feature = "12-48-0")]
127#[cfg_attr(docsrs, doc(cfg(feature = "12-48-0")))]
128impl<C: Client> DriveFileUrlBuilder<C>
129where
130    C: StreamingClient<Error = <C as Client>::Error>,
131{
132    /// Uploads the file and wait for a message of completion from the server.
133    ///
134    /// Unlike [`upload`][upload], this method returns [`DriveFile`][drive_file].
135    ///
136    /// [upload]: DriveFileUrlBuilder::upload
137    /// [drive_file]: misskey_api::model::drive::DriveFile
138    ///
139    /// # Note on the use of main stream
140    ///
141    /// This method is implemented by waiting for the upload completion event in the main stream.
142    /// However, it is currently not possible to have multiple connections to the main stream from
143    /// the same client. Therefore, when you use this method, you must not not be connected to the
144    /// main stream elsewhere. Likewise, you will not be able to connect to the main stream until
145    /// this method is completed.
146    pub async fn upload_and_wait(&self) -> Result<DriveFile, Error<<C as Client>::Error>> {
147        let expected_marker = self.marker.clone();
148        self.client
149            .request(&self.request)
150            .await
151            .map_err(Error::Client)?
152            .into_result()?;
153
154        use channel::main::{self, MainStreamEvent};
155        let stream = self
156            .client
157            .channel(main::Request::default())
158            .await
159            .map_err(Error::Client)?
160            .map_err(Error::Client)
161            .try_filter_map(|event| async {
162                match event {
163                    MainStreamEvent::UrlUploadFinished {
164                        marker: Some(marker),
165                        file,
166                    } if marker == expected_marker => Ok(Some(file)),
167                    _ => Ok(None),
168                }
169            });
170        futures::pin_mut!(stream);
171        let file = stream.try_next().await?.unwrap();
172        Ok(file)
173    }
174}
175
176/// Builder for the [`build_file`][`crate::UploadFileClientExt::build_file`] method.
177pub struct DriveFileBuilder<C> {
178    client: C,
179    path: PathBuf,
180    type_: Mime,
181    request: endpoint::drive::files::create::Request,
182}
183
184impl<C> DriveFileBuilder<C> {
185    /// Creates a builder with the client and path to the file.
186    pub fn with_path(client: C, path: impl AsRef<Path>) -> Self {
187        let path = path.as_ref().to_owned();
188        let request = endpoint::drive::files::create::Request {
189            name: path.file_name().map(|s| s.to_string_lossy().into_owned()),
190            folder_id: None,
191            is_sensitive: Some(false),
192            force: Some(false),
193        };
194        let type_ = mime_guess::from_path(&path).first_or_octet_stream();
195        DriveFileBuilder {
196            client,
197            type_,
198            path,
199            request,
200        }
201    }
202
203    /// Gets the request object for reuse.
204    pub fn as_request(&self) -> &endpoint::drive::files::create::Request {
205        &self.request
206    }
207
208    /// Sets the parent folder of the file.
209    pub fn folder(&mut self, folder: impl EntityRef<DriveFolder>) -> &mut Self {
210        self.request.folder_id.replace(folder.entity_ref());
211        self
212    }
213
214    /// Sets the mime type of the file.
215    pub fn type_(&mut self, type_: Mime) -> &mut Self {
216        self.type_ = type_;
217        self
218    }
219
220    /// Sets the name of the file.
221    pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
222        self.request.name.replace(name.into());
223        self
224    }
225
226    /// Sets whether the file contains NSFW content.
227    pub fn sensitive(&mut self, sensitive: bool) -> &mut Self {
228        self.request.is_sensitive = Some(sensitive);
229        self
230    }
231
232    /// Sets whether or not to upload the file again even if the same content has already been
233    /// uploaded.
234    pub fn use_existing_if_uploaded(&mut self, use_existing_if_uploaded: bool) -> &mut Self {
235        self.request.force = Some(!use_existing_if_uploaded);
236        self
237    }
238}
239
240impl<C: UploadFileClient> DriveFileBuilder<C> {
241    /// Uploads the file.
242    pub async fn upload(&self) -> Result<DriveFile, Error<C::Error>> {
243        let fs_file = std::fs::File::open(&self.path)?;
244        let file = self
245            .client
246            .request_with_file(
247                &self.request,
248                self.type_.clone(),
249                self.request.name.clone().unwrap_or_default(),
250                fs_file,
251            )
252            .await
253            .map_err(Error::Client)?
254            .into_result()?;
255        Ok(file)
256    }
257}
258
259/// Builder for the [`update_file`][`crate::ClientExt::update_file`] method.
260pub struct DriveFileUpdateBuilder<C> {
261    client: C,
262    request: endpoint::drive::files::update::Request,
263}
264
265impl<C> DriveFileUpdateBuilder<C> {
266    /// Creates a builder with the client and the file you are going to update.
267    pub fn new(client: C, file: impl EntityRef<DriveFile>) -> Self {
268        let request = endpoint::drive::files::update::Request {
269            file_id: file.entity_ref(),
270            folder_id: None,
271            name: None,
272            is_sensitive: None,
273        };
274        DriveFileUpdateBuilder { client, request }
275    }
276
277    /// Gets the request object for reuse.
278    pub fn as_request(&self) -> &endpoint::drive::files::update::Request {
279        &self.request
280    }
281
282    /// Sets the parent folder of the file.
283    pub fn set_folder(&mut self, folder: impl EntityRef<DriveFolder>) -> &mut Self {
284        self.request.folder_id.replace(Some(folder.entity_ref()));
285        self
286    }
287
288    /// Deletes the parent folder of the file.
289    pub fn delete_folder(&mut self) -> &mut Self {
290        self.request.folder_id.replace(None);
291        self
292    }
293
294    /// Sets the name of the file.
295    pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
296        self.request.name.replace(name.into());
297        self
298    }
299
300    /// Sets whether the file contains NSFW content.
301    pub fn sensitive(&mut self, sensitive: bool) -> &mut Self {
302        self.request.is_sensitive = Some(sensitive);
303        self
304    }
305}
306
307impl<C: Client> DriveFileUpdateBuilder<C> {
308    /// Updates the file.
309    pub async fn update(&self) -> Result<DriveFile, Error<C::Error>> {
310        let file = self
311            .client
312            .request(&self.request)
313            .await
314            .map_err(Error::Client)?
315            .into_result()?;
316        Ok(file)
317    }
318}
319
320/// Builder for the [`update_folder`][`crate::ClientExt::update_folder`] method.
321pub struct DriveFolderUpdateBuilder<C> {
322    client: C,
323    request: endpoint::drive::folders::update::Request,
324}
325
326impl<C> DriveFolderUpdateBuilder<C> {
327    /// Creates a builder with the client and the folder you are going to update.
328    pub fn new(client: C, folder: impl EntityRef<DriveFolder>) -> Self {
329        let request = endpoint::drive::folders::update::Request {
330            folder_id: folder.entity_ref(),
331            parent_id: None,
332            name: None,
333        };
334        DriveFolderUpdateBuilder { client, request }
335    }
336
337    /// Gets the request object for reuse.
338    pub fn as_request(&self) -> &endpoint::drive::folders::update::Request {
339        &self.request
340    }
341
342    update_builder_option_field! {
343        #[doc_name = "parent folder of the folder"]
344        pub parent: impl EntityRef<DriveFolder> { parent_id = parent.entity_ref() };
345    }
346
347    /// Sets the name of the folder.
348    pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
349        self.request.name.replace(name.into());
350        self
351    }
352}
353
354impl<C: Client> DriveFolderUpdateBuilder<C> {
355    /// Updates the folder.
356    pub async fn update(&self) -> Result<DriveFolder, Error<C::Error>> {
357        let folder = self
358            .client
359            .request(&self.request)
360            .await
361            .map_err(Error::Client)?
362            .into_result()?;
363        Ok(folder)
364    }
365}
366
367/// Builder for the [`files`][`crate::ClientExt::files`] method.
368pub struct DriveFileListBuilder<C> {
369    client: C,
370    request: endpoint::drive::files::Request,
371}
372
373impl<C> DriveFileListBuilder<C> {
374    /// Creates a builder with the client.
375    pub fn new(client: C) -> Self {
376        let request = endpoint::drive::files::Request::default();
377        DriveFileListBuilder { client, request }
378    }
379
380    /// Gets the request object for reuse.
381    pub fn as_request(&self) -> &endpoint::drive::files::Request {
382        &self.request
383    }
384
385    /// Limits the listed files to those of the specified MIME type.
386    pub fn type_(&mut self, type_: Mime) -> &mut Self {
387        self.request.type_.replace(type_);
388        self
389    }
390
391    /// Specifies the folder to list the files.
392    pub fn folder(&mut self, folder: impl EntityRef<DriveFolder>) -> &mut Self {
393        self.request.folder_id.replace(folder.entity_ref());
394        self
395    }
396}
397
398impl<C: Client + Sync> DriveFileListBuilder<C> {
399    /// Lists the files.
400    pub fn list(&self) -> PagerStream<BoxPager<C, DriveFile>> {
401        let pager = BackwardPager::new(&self.client, self.request.clone());
402        PagerStream::new(Box::pin(pager))
403    }
404}