google_drive/
traits.rs

1#![allow(clippy::field_reassign_with_default)]
2use crate::{ClientError, ClientResult, Response};
3
4#[async_trait::async_trait]
5pub trait PermissionOps {
6    /// Add a permission if it does not already exist.
7    ///
8    /// `role`: The role granted by this permission. While new values may be supported in the future, the following are currently allowed:
9    /// - `owner`
10    /// - `organizer`
11    /// - `fileOrganizer`
12    /// - `writer`
13    /// - `commenter`
14    /// - `reader`
15    ///
16    /// `type_`: The type of the grantee. Valid values are:
17    /// - `user`
18    /// - `group`
19    /// - `domain`
20    /// - `anyone`
21    /// When creating a permission, if type is user or group, you must provide an emailAddress for the user or group. When type is domain, you must provide a domain. There isn't extra information required for a anyone type.
22    async fn add_if_not_exists(
23        &self,
24        file_id: &str,
25        email_address: &str,
26        email_message: &str,
27        role: &str,
28        type_: &str,
29        use_domain_admin_access: bool,
30        send_notification_email: bool,
31    ) -> ClientResult<Response<crate::types::Permission>>;
32}
33
34#[async_trait::async_trait]
35impl PermissionOps for crate::permissions::Permissions {
36    /// Add a permission if it does not already exist.
37    ///
38    /// `role`: The role granted by this permission. While new values may be supported in the future, the following are currently allowed:
39    /// - `owner`
40    /// - `organizer`
41    /// - `fileOrganizer`
42    /// - `writer`
43    /// - `commenter`
44    /// - `reader`
45    ///
46    /// `type_`: The type of the grantee. Valid values are:
47    /// - `user`
48    /// - `group`
49    /// - `domain`
50    /// - `anyone`
51    /// When creating a permission, if type is user or group, you must provide an emailAddress for the user or group. When type is domain, you must provide a domain. There isn't extra information required for a anyone type.
52    async fn add_if_not_exists(
53        &self,
54        file_id: &str,
55        email_address: &str,
56        email_message: &str,
57        role: &str,
58        type_: &str,
59        use_domain_admin_access: bool,
60        send_notification_email: bool,
61    ) -> ClientResult<Response<crate::types::Permission>> {
62        // First let's check if the permission already exists.
63        // List all the permissions for a file.
64        let resp = self
65            .list_all(
66                file_id,
67                "",   // include_permissions_for_view
68                true, // supports_all_drives
69                true, // supports_team_drives
70                use_domain_admin_access,
71            )
72            .await?;
73
74        // Iterate over our permissions and see if we have ours.
75        for perm in resp.body {
76            if perm.email_address == email_address && perm.role == role && perm.type_ == type_ {
77                // We found the permission, return it.
78                return Ok(Response::new(resp.status, resp.headers, perm));
79            }
80        }
81
82        // If we got here we could not find the permission so let's create it.
83        let perm = crate::types::Permission {
84            allow_file_discovery: None,
85            deleted: None,
86            display_name: String::new(),
87            domain: String::new(),
88            email_address: email_address.to_string(),
89            expiration_time: None,
90            id: String::new(),
91            kind: String::new(),
92            permission_details: Default::default(),
93            photo_link: String::new(),
94            role: role.to_string(),
95            team_drive_permission_details: Default::default(),
96            type_: type_.to_string(),
97            view: String::new(),
98        };
99
100        // Create the permission and return it.
101        self.create(
102            file_id,
103            email_message,
104            false, // move_to_new_owners_root
105            send_notification_email,
106            true,  // supports_all_drives
107            true,  // supports_team_drives
108            false, // transfer_ownership
109            use_domain_admin_access,
110            &perm,
111        )
112        .await
113    }
114}
115
116#[async_trait::async_trait]
117pub trait FileOps {
118    /// Get a file by it's name.
119    async fn get_by_name(
120        &self,
121        drive_id: &str,
122        parent_id: &str,
123        name: &str,
124    ) -> ClientResult<Response<Vec<crate::types::File>>>;
125
126    /// Create or update a file in a drive.
127    /// If the file already exists, it will update it.
128    /// If the file does not exist, it will create it.
129    async fn create_or_update(
130        &self,
131        drive_id: &str,
132        parent_id: &str,
133        name: &str,
134        mime_type: &str,
135        contents: &[u8],
136    ) -> ClientResult<Response<crate::types::File>>;
137
138    /// Download a file by it's ID.
139    async fn download_by_id(&self, id: &str) -> ClientResult<Response<bytes::Bytes>>;
140
141    /// Create a folder, if it doesn't exist, returns the ID of the folder.
142    async fn create_folder(
143        &self,
144        drive_id: &str,
145        parent_id: &str,
146        name: &str,
147    ) -> ClientResult<Response<crate::types::File>>;
148
149    /// Get a file's contents by it's ID. Only works for Google Docs.
150    async fn get_contents_by_id(&self, id: &str) -> ClientResult<Response<String>>;
151
152    /// Delete a file by its name.
153    async fn delete_by_name(
154        &self,
155        drive_id: &str,
156        parent_id: &str,
157        name: &str,
158    ) -> ClientResult<Response<()>>;
159}
160
161#[async_trait::async_trait]
162impl FileOps for crate::files::Files {
163    /// Get a file by it's name.
164    async fn get_by_name(
165        &self,
166        drive_id: &str,
167        parent_id: &str,
168        name: &str,
169    ) -> ClientResult<Response<Vec<crate::types::File>>> {
170        let mut query = format!("name = '{}'", name);
171        if !parent_id.is_empty() {
172            query = format!("{} and '{}' in parents", query, parent_id);
173        }
174
175        self.list_all(
176            "drive",  // corpora
177            drive_id, // drive id
178            true,     // include_items_from_all_drives
179            "",       // include_permissions_for_view
180            false,    // include_team_drive_items
181            "",       // order_by
182            &query,   // query
183            "",       // spaces
184            true,     // supports_all_drives
185            false,    // supports_team_drives
186            "",       // team_drive_id
187        )
188        .await
189    }
190
191    /// Create or update a file in a drive.
192    /// If the file already exists, it will update it.
193    /// If the file does not exist, it will create it.
194    async fn create_or_update(
195        &self,
196        drive_id: &str,
197        parent_id: &str,
198        name: &str,
199        mime_type: &str,
200        contents: &[u8],
201    ) -> ClientResult<Response<crate::types::File>> {
202        // Create the file.
203        let mut f: crate::types::File = Default::default();
204        let mut method = reqwest::Method::POST;
205        let mut uri = "https://www.googleapis.com/upload/drive/v3/files".to_string();
206
207        // Check if the file exists.
208        let files = self
209            .get_by_name(drive_id, parent_id, name)
210            .await
211            .map(|resp| resp.body)
212            .unwrap_or_default();
213        if files.is_empty() {
214            // Set the name,
215            f.name = name.to_string();
216            f.mime_type = mime_type.to_string();
217            if !parent_id.is_empty() {
218                f.parents = vec![parent_id.to_string()];
219            } else {
220                f.parents = vec![drive_id.to_string()];
221            }
222
223            uri += "?uploadType=resumable&supportsAllDrives=true&includeItemsFromAllDrives=true";
224
225            // Create the file.
226        } else if let Some(f) = files.first() {
227            method = reqwest::Method::PATCH;
228            let mut f = f.clone();
229            uri += &format!(
230                "/{}?uploadType=resumable&supportsAllDrives=true&includeItemsFromAllDrives=true",
231                f.id
232            );
233
234            f.id = "".to_string();
235            f.drive_id = "".to_string();
236            f.kind = "".to_string();
237            f.original_filename = f.name.to_string();
238        } else {
239            // Set the name,
240            f.name = name.to_string();
241            f.mime_type = mime_type.to_string();
242            if !parent_id.is_empty() {
243                f.parents = vec![parent_id.to_string()];
244            } else {
245                f.parents = vec![drive_id.to_string()];
246            }
247
248            uri += "?uploadType=resumable&supportsAllDrives=true&includeItemsFromAllDrives=true";
249
250            // Create the file.
251        }
252
253        // Build the request to get the URL upload location if we need to create the file.
254        let resp = self
255            .client
256            .request_raw(
257                method,
258                &uri,
259                crate::Message {
260                    body: Some(reqwest::Body::from(serde_json::to_vec(&f)?)),
261                    content_type: None,
262                },
263            )
264            .await?;
265
266        // Get the "Location" header.
267        let location = resp
268            .headers()
269            .get("Location")
270            .ok_or(ClientError::HttpError {
271                status: resp.status(),
272                headers: resp.headers().clone(),
273                error: "Missing Location header".to_string(),
274            })?
275            .to_str()?;
276
277        // Now upload the file to that location.
278        self.client
279            .request_with_mime(reqwest::Method::PUT, location, contents, mime_type)
280            .await
281    }
282
283    /// Download a file by it's ID.
284    async fn download_by_id(&self, id: &str) -> ClientResult<Response<bytes::Bytes>> {
285        let resp = self
286            .client
287            .request_raw(
288                reqwest::Method::GET,
289                &self.client.url(
290                    &format!("/files/{}?supportsAllDrives=true&alt=media", id),
291                    None,
292                ),
293                crate::Message::default(),
294            )
295            .await?;
296
297        Ok(Response::new(
298            resp.status(),
299            resp.headers().clone(),
300            resp.bytes().await?,
301        ))
302    }
303
304    /// Create a folder, if it doesn't exist, returns the ID of the folder.
305    async fn create_folder(
306        &self,
307        drive_id: &str,
308        parent_id: &str,
309        name: &str,
310    ) -> ClientResult<Response<crate::types::File>> {
311        let folder_mime_type = "application/vnd.google-apps.folder";
312        let mut file: crate::types::File = Default::default();
313        // Set the name,
314        file.name = name.to_string();
315        file.mime_type = folder_mime_type.to_string();
316        if !parent_id.is_empty() {
317            file.parents = vec![parent_id.to_string()];
318        } else {
319            file.parents = vec![drive_id.to_string()];
320        }
321
322        let mut query = format!(
323            "name = '{}' and mimeType = 'application/vnd.google-apps.folder'",
324            name
325        );
326        if !parent_id.is_empty() {
327            query = format!("{} and '{}' in parents", query, parent_id);
328        }
329
330        // Check if the folder exists.
331        let resp = self
332            .list_all(
333                "drive",  // corpora
334                drive_id, // drive id
335                true,     // include_items_from_all_drives
336                "",       // include_permissions_for_view
337                false,    // include_team_drive_items
338                "",       // order_by
339                &query,   // query
340                "",       // spaces
341                true,     // supports_all_drives
342                false,    // supports_team_drives
343                "",       // team_drive_id
344            )
345            .await?;
346
347        if !resp.body.is_empty() {
348            return Ok(Response::new(
349                resp.status,
350                resp.headers,
351                resp.body.into_iter().next().unwrap(),
352            ));
353        }
354
355        // Make the request and return the ID.
356        let resp: Response<crate::types::File> = self
357            .client
358            .post(
359                &self.client.url(
360                    "/files?supportsAllDrives=true&includeItemsFromAllDrives=true",
361                    None,
362                ),
363                crate::Message {
364                    body: Some(reqwest::Body::from(serde_json::to_vec(&file)?)),
365                    content_type: None,
366                },
367            )
368            .await?;
369
370        Ok(resp)
371    }
372
373    /// Get a file's contents by it's ID. Only works for Google Docs.
374    // TODO: make binary content work in the actual library.
375    async fn get_contents_by_id(&self, id: &str) -> ClientResult<Response<String>> {
376        let mut query_ = String::new();
377        let query_args = ["mime_type=text/plain".to_string()];
378        for (i, n) in query_args.iter().enumerate() {
379            if i > 0 {
380                query_.push('&');
381            }
382            query_.push_str(n);
383        }
384        let url = self.client.url(
385            &format!(
386                "/files/{}/export?{}",
387                crate::progenitor_support::encode_path(id),
388                query_
389            ),
390            None,
391        );
392        let resp = self
393            .client
394            .request_raw(reqwest::Method::GET, &url, crate::Message::default())
395            .await?;
396
397        Ok(Response::new(
398            resp.status(),
399            resp.headers().clone(),
400            resp.text().await?,
401        ))
402    }
403
404    /// Delete a file by its name.
405    async fn delete_by_name(
406        &self,
407        drive_id: &str,
408        parent_id: &str,
409        name: &str,
410    ) -> ClientResult<Response<()>> {
411        // Check if the file exists.
412        let resp = self.get_by_name(drive_id, parent_id, name).await?;
413
414        if resp.body.is_empty() {
415            // The file does not exist.
416            return Ok(Response::new(resp.status, resp.headers, ()));
417        }
418
419        // Delete the file.
420        self.delete(
421            &resp.body.first().unwrap().id,
422            true, // supports all drives
423            true, // supports team drives
424        )
425        .await
426    }
427}
428
429#[async_trait::async_trait]
430pub trait DriveOps {
431    /// Get a drive by it's name.
432    async fn get_by_name(&self, name: &str) -> ClientResult<Response<crate::types::Drive>>;
433}
434
435#[async_trait::async_trait]
436impl DriveOps for crate::drives::Drives {
437    /// Get a drive by it's name.
438    async fn get_by_name(&self, name: &str) -> ClientResult<Response<crate::types::Drive>> {
439        let resp = self
440            .list_all(
441                //&format!("name = '{}'", name), // query
442                "", true, // use domain admin access
443            )
444            .await?;
445
446        for drive in resp.body {
447            if drive.name == name {
448                return Ok(Response::new(resp.status, resp.headers, drive));
449            }
450        }
451
452        Err(ClientError::DriveNotFound {
453            name: name.to_string(),
454        })
455    }
456}