drive_v3/resources/
revisions.rs

1use std::{fs, path::PathBuf};
2use reqwest::{blocking::Client, Method, Url};
3use drive_v3_macros::{DriveRequestBuilder, request};
4
5use super::DriveRequestBuilder;
6use crate::{objects, Credentials};
7
8#[request(
9    method=Method::DELETE,
10    url="https://www.googleapis.com/drive/v3/files/{file_id}/revisions/{revision_id}",
11    returns=(),
12)]
13#[derive(DriveRequestBuilder)]
14/// A request builder to permanently delete a file version.
15pub struct DeleteRequest {}
16
17#[request(
18    method=Method::GET,
19    url="https://www.googleapis.com/drive/v3/files/{file_id}/revisions/{revision_id}",
20    returns=objects::Revision,
21)]
22#[derive(DriveRequestBuilder)]
23/// A request builder to get a revision's metadata by ID.
24pub struct GetRequest {}
25
26#[request(
27    method=Method::GET,
28    url="https://www.googleapis.com/drive/v3/files/{file_id}/revisions/{revision_id}",
29)]
30#[derive(DriveRequestBuilder)]
31/// A request builder to get a revision's content by ID.
32pub struct GetMediaRequest {
33    /// Whether the user is acknowledging the risk of downloading known
34    /// malware or other abusive files.
35    #[drive_v3(parameter)]
36    acknowledge_abuse: Option<bool>,
37
38    /// Path to save the contents to.
39    save_to: Option<PathBuf>,
40}
41
42impl GetMediaRequest {
43    /// Executes this request.
44    ///
45    /// # Errors:
46    ///
47    /// - a [`UrlParsing`](crate::ErrorKind::UrlParsing) error, if the creation
48    /// of the request's URL failed.
49    /// - a [`Request`](crate::ErrorKind::Request) error, if unable to send the
50    /// request or get a body from the response.
51    /// - a [`Response`](crate::ErrorKind::Response) error, if the request
52    /// returned an error response.
53    /// - a [`Json`](crate::ErrorKind::Json) error, if unable to parse the
54    /// response's body.
55    pub fn execute( &self ) -> crate::Result< Vec<u8> > {
56        let mut parameters = self.get_parameters();
57        parameters.push(( "alt".into(), objects::Alt::Media.to_string() ));
58
59        let url = Url::parse_with_params(&self.url, parameters)?;
60        let request = Client::new()
61            .request( self.method.clone(), url )
62            .bearer_auth( self.credentials.get_access_token() );
63
64        let response = request.send()?;
65
66        if !response.status().is_success() {
67            return Err( response.into() );
68        }
69
70        let file_bytes: Vec<u8> = response.bytes()?.into();
71
72        if let Some(path) = &self.save_to {
73            use std::io::Write;
74
75            let mut file = fs::File::create(path)?;
76            file.write_all(&file_bytes)?;
77        }
78
79        Ok(file_bytes)
80    }
81}
82
83#[request(
84    method=Method::GET,
85    url="https://www.googleapis.com/drive/v3/files/{file_id}/revisions",
86    returns=objects::RevisionList,
87)]
88#[derive(DriveRequestBuilder)]
89/// A request builder to list a file's revisions.
90pub struct ListRequest {
91    /// The maximum number of permissions to return per page.
92    ///
93    /// When not set for files in a shared drive, at most 100 results will
94    /// be returned. When not set for files that are not in a shared drive,
95    /// the entire list will be returned.
96    #[drive_v3(parameter)]
97    page_size: Option<i64>,
98
99    /// The token for continuing a previous list request on the next page.
100    ///
101    /// This should be set to the value of
102    /// [`next_page_token`](objects::RevisionList::next_page_token) from
103    /// the previous response.
104    #[drive_v3(parameter)]
105    page_token: Option<String>,
106}
107
108#[request(
109    method=Method::PATCH,
110    url="https://www.googleapis.com/drive/v3/files/{file_id}/revisions/{revision_id}",
111    returns=objects::Revision,
112)]
113#[derive(DriveRequestBuilder)]
114/// A request builder to update a revision with patch semantics.
115pub struct UpdateRequest {
116    /// The updated revision.
117    #[drive_v3(body)]
118    revision: Option<objects::Revision>,
119}
120
121/// The metadata for a revision to a file.
122///
123/// Some resource methods (such as [`revisions.update`](Revisions::update))
124/// require a `permission_id`. Use the [`revisions.list`](Revisions::list)
125/// method to retrieve the ID for a file, folder, or shared drive.
126///
127/// # Examples:
128///
129/// List the revisions in a file
130///
131/// ```no_run
132/// # use drive_v3::{Error, Credentials, Drive};
133/// #
134/// # let drive = Drive::new( &Credentials::from_file(
135/// #     "../.secure-files/google_drive_credentials.json",
136/// #     &["https://www.googleapis.com/auth/drive.file"],
137/// # )? );
138/// #
139/// let file_id = "some-file-id";
140///
141/// let revision_list = drive.revisions.list(&file_id)
142///     .execute()?;
143///
144/// if let Some(revisions) = revision_list.revisions {
145///     for revision in revisions {
146///         println!("{}", revision);
147///     }
148/// }
149/// # Ok::<(), Error>(())
150/// ```
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct Revisions {
153    /// Credentials used to authenticate a user's access to this resource.
154    credentials: Credentials,
155}
156
157impl Revisions {
158    /// Creates a new [`Revisions`] resource with the given [`Credentials`].
159    pub fn new( credentials: &Credentials ) -> Self {
160        Self { credentials: credentials.clone() }
161    }
162
163    /// Permanently deletes a file version.
164    ///
165    /// You can only delete revisions for files with binary content in Google
166    /// Drive, like images or videos. Revisions for other files, like Google
167    /// Docs or Sheets, and the last remaining file version can't be deleted.
168    ///
169    /// See Google's
170    /// [documentation](https://developers.google.com/drive/api/reference/rest/v3/revisions/delete)
171    /// for more information.
172    ///
173    /// # Requires one of the following OAuth scopes:
174    ///
175    /// - `https://www.googleapis.com/auth/drive`
176    /// - `https://www.googleapis.com/auth/drive.appdata`
177    /// - `https://www.googleapis.com/auth/drive.file`
178    ///
179    /// # Examples:
180    ///
181    /// ```no_run
182    /// # use drive_v3::{Error, Credentials, Drive};
183    /// #
184    /// # let drive = Drive::new( &Credentials::from_file(
185    /// #     "../.secure-files/google_drive_credentials.json",
186    /// #     &["https://www.googleapis.com/auth/drive.file"],
187    /// # )? );
188    /// #
189    /// let file_id = "some-file-id";
190    /// let revision_id = "some-revision-id";
191    ///
192    /// let response = drive.revisions.delete(&file_id, &revision_id).execute();
193    ///
194    /// assert!( response.is_ok() );
195    /// # Ok::<(), Error>(())
196    /// ```
197    pub fn delete<T, U> ( &self, file_id: T, revision_id: U ) -> DeleteRequest
198        where
199            T: AsRef<str>,
200            U: AsRef<str>,
201    {
202        DeleteRequest::new(&self.credentials, file_id, revision_id)
203    }
204
205    /// Gets a revision's metadata by ID.
206    ///
207    /// See Google's
208    /// [documentation](https://developers.google.com/drive/api/reference/rest/v3/revisions/get)
209    /// for more information.
210    ///
211    /// # Requires one of the following OAuth scopes:
212    ///
213    /// - `https://www.googleapis.com/auth/drive`
214    /// - `https://www.googleapis.com/auth/drive.appdata`
215    /// - `https://www.googleapis.com/auth/drive.file`
216    /// - `https://www.googleapis.com/auth/drive.metadata`
217    /// - `https://www.googleapis.com/auth/drive.metadata.readonly`
218    /// - `https://www.googleapis.com/auth/drive.photos.readonly`
219    /// - `https://www.googleapis.com/auth/drive.readonly`
220    ///
221    /// # Examples:
222    ///
223    /// ```no_run
224    /// # use drive_v3::{Error, Credentials, Drive};
225    /// #
226    /// # let drive = Drive::new( &Credentials::from_file(
227    /// #     "../.secure-files/google_drive_credentials.json",
228    /// #     &["https://www.googleapis.com/auth/drive.file"],
229    /// # )? );
230    /// #
231    /// let file_id = "some-file-id";
232    /// let revision_id = "some-revision-id";
233    ///
234    /// let revision = drive.revisions.get(&file_id, &revision_id)
235    ///     .fields("*")
236    ///     .execute()?;
237    ///
238    /// println!("This is the file's revision metadata:\n{}", revision);
239    /// # Ok::<(), Error>(())
240    /// ```
241    pub fn get<T, U> ( &self, file_id: T, revision_id: U ) -> GetRequest
242        where
243            T: AsRef<str>,
244            U: AsRef<str>,
245    {
246        GetRequest::new(&self.credentials, file_id, revision_id)
247    }
248
249    /// Gets a revision's content by ID.
250    ///
251    /// See Google's
252    /// [documentation](https://developers.google.com/drive/api/reference/rest/v3/revisions/get)
253    /// for more information.
254    ///
255    /// # Requires one of the following OAuth scopes:
256    ///
257    /// - `https://www.googleapis.com/auth/drive`
258    /// - `https://www.googleapis.com/auth/drive.appdata`
259    /// - `https://www.googleapis.com/auth/drive.file`
260    /// - `https://www.googleapis.com/auth/drive.metadata`
261    /// - `https://www.googleapis.com/auth/drive.metadata.readonly`
262    /// - `https://www.googleapis.com/auth/drive.photos.readonly`
263    /// - `https://www.googleapis.com/auth/drive.readonly`
264    ///
265    /// # Examples:
266    ///
267    /// ```no_run
268    /// # use drive_v3::{Error, Credentials, Drive};
269    /// #
270    /// # let drive = Drive::new( &Credentials::from_file(
271    /// #     "../.secure-files/google_drive_credentials.json",
272    /// #     &["https://www.googleapis.com/auth/drive.file"],
273    /// # )? );
274    /// #
275    /// let file_id = "some-file-id";
276    /// let revision_id = "some-revision-id";
277    ///
278    /// let file_bytes = drive.revisions.get_media(&file_id, &revision_id)
279    ///     // .save_to("my_downloaded_file.txt") // Save the contents to a path
280    ///     .execute()?;
281    ///
282    /// let content = String::from_utf8_lossy(&file_bytes);
283    ///
284    /// println!("content: {}", content);
285    /// # Ok::<(), Error>(())
286    /// ```
287    pub fn get_media<T, U> ( &self, file_id: T, revision_id: U ) -> GetMediaRequest
288        where
289            T: AsRef<str>,
290            U: AsRef<str>,
291    {
292        GetMediaRequest::new(&self.credentials, file_id, revision_id)
293    }
294
295    /// Lists a file's revisions.
296    ///
297    /// See Google's
298    /// [documentation](https://developers.google.com/drive/api/reference/rest/v3/revisions/list)
299    /// for more information.
300    ///
301    /// # Requires one of the following OAuth scopes:
302    ///
303    /// - `https://www.googleapis.com/auth/drive`
304    /// - `https://www.googleapis.com/auth/drive.appdata`
305    /// - `https://www.googleapis.com/auth/drive.file`
306    /// - `https://www.googleapis.com/auth/drive.metadata`
307    /// - `https://www.googleapis.com/auth/drive.metadata.readonly`
308    /// - `https://www.googleapis.com/auth/drive.photos.readonly`
309    /// - `https://www.googleapis.com/auth/drive.readonly`
310    ///
311    /// # Examples:
312    ///
313    /// ```no_run
314    /// # use drive_v3::{Error, Credentials, Drive};
315    /// #
316    /// # let drive = Drive::new( &Credentials::from_file(
317    /// #     "../.secure-files/google_drive_credentials.json",
318    /// #     &["https://www.googleapis.com/auth/drive.file"],
319    /// # )? );
320    /// #
321    /// let file_id = "some-file-id";
322    ///
323    /// let revision_list = drive.revisions.list(&file_id)
324    ///     .execute()?;
325    ///
326    /// if let Some(revisions) = revision_list.revisions {
327    ///     for revision in revisions {
328    ///         println!("{}", revision);
329    ///     }
330    /// }
331    /// # Ok::<(), Error>(())
332    /// ```
333    pub fn list<T: AsRef<str>> ( &self, file_id: T ) -> ListRequest {
334        ListRequest::new(&self.credentials, file_id)
335    }
336
337    /// Updates a reply with patch semantics.
338    ///
339    /// See Google's
340    /// [documentation](https://developers.google.com/drive/api/reference/rest/v3/replies/update)
341    /// for more information.
342    ///
343    /// # Note:
344    ///
345    /// This request requires you to set the [`fields`](UpdateRequest::fields)
346    /// parameter.
347    ///
348    /// # Requires one of the following OAuth scopes:
349    ///
350    /// - `https://www.googleapis.com/auth/drive`
351    /// - `https://www.googleapis.com/auth/drive.file`
352    ///
353    /// # Examples:
354    ///
355    /// ```no_run
356    /// use drive_v3::objects::Revision;
357    /// # use drive_v3::{Error, Credentials, Drive};
358    /// #
359    /// # let drive = Drive::new( &Credentials::from_file(
360    /// #     "../.secure-files/google_drive_credentials.json",
361    /// #     &["https://www.googleapis.com/auth/drive.file"],
362    /// # )? );
363    ///
364    /// let updated_revision = Revision {
365    ///     published: Some(false),
366    ///     ..Default::default()
367    /// };
368    ///
369    /// let file_id = "some-file-id";
370    /// let revision_id = "some-revision-id";
371    ///
372    /// let revision = drive.revisions.update(&file_id, &revision_id)
373    ///     .fields("*")
374    ///     .revision(&updated_revision)
375    ///     .execute()?;
376    ///
377    /// assert_eq!(revision.published, updated_revision.published);
378    /// # Ok::<(), Error>(())
379    /// ```
380    pub fn update<T, U> ( &self, file_id: T, revision_id: U ) -> UpdateRequest
381        where
382            T: AsRef<str>,
383            U: AsRef<str>,
384    {
385        UpdateRequest::new(&self.credentials, file_id, revision_id)
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use std::fs;
392    use super::Revisions;
393    use std::path::PathBuf;
394    use crate::{ErrorKind, objects, resources};
395    use crate::utils::test::{INVALID_CREDENTIALS, LOCAL_STORAGE_IN_USE, VALID_CREDENTIALS};
396
397    fn get_resource() -> Revisions {
398        Revisions::new(&VALID_CREDENTIALS)
399    }
400
401    fn get_invalid_resource() -> Revisions {
402        Revisions::new(&INVALID_CREDENTIALS)
403    }
404
405    fn get_files_resource() -> resources::Files {
406        resources::Files::new(&VALID_CREDENTIALS)
407    }
408
409    fn delete_file( file: &objects::File ) -> crate::Result<()> {
410        get_files_resource().delete( file.clone().id.unwrap() ).execute()
411    }
412
413    fn get_test_file_metadata() -> objects::File {
414        objects::File {
415            name: Some( "test.txt".to_string() ),
416            description: Some( "a test file".to_string() ),
417            mime_type: Some( "text/plain".to_string() ),
418            ..Default::default()
419        }
420    }
421
422    fn get_test_drive_file() -> crate::Result<objects::File> {
423        let metadata = get_test_file_metadata();
424
425        let file = get_files_resource().create()
426            .upload_type(objects::UploadType::Multipart)
427            .metadata(&metadata)
428            .content_string("content")
429            .execute()?;
430
431
432        get_files_resource().update( &file.clone().id.unwrap() )
433            .upload_type(objects::UploadType::Multipart)
434            .metadata(&metadata)
435            .content_string("content")
436            .execute()
437    }
438
439    fn get_oldest_file_revision( file: &objects::File ) -> crate::Result<objects::Revision> {
440        let revision_list = get_resource().list( &file.clone().id.unwrap() )
441            .fields("*")
442            .execute()?;
443
444        Ok( revision_list.revisions.unwrap().last().unwrap().clone() )
445    }
446
447    #[test]
448    fn new_test() {
449        let valid_resource = get_resource();
450        let invalid_resource = get_invalid_resource();
451
452        assert_eq!( valid_resource.credentials, VALID_CREDENTIALS.clone() );
453        assert_eq!( invalid_resource.credentials, INVALID_CREDENTIALS.clone() );
454    }
455
456    #[test]
457    fn delete_test() {
458        let test_drive_file = get_test_drive_file().unwrap();
459        let oldest_file_revision = get_oldest_file_revision(&test_drive_file).unwrap();
460
461        let response = get_resource().delete(
462            &test_drive_file.clone().id.unwrap(),
463            &oldest_file_revision.clone().id.unwrap(),
464            )
465            .execute();
466
467        assert!( response.is_ok() );
468
469        delete_file(&test_drive_file).expect("Failed to cleanup created file");
470    }
471
472    #[test]
473    fn delete_invalid_test() {
474        let response = get_invalid_resource().delete("invalid-id", "invalid-id")
475            .execute();
476
477        assert!( response.is_err() );
478        assert_eq!( response.unwrap_err().kind, ErrorKind::Response );
479    }
480
481    #[test]
482    fn get_test() {
483        let test_drive_file = get_test_drive_file().unwrap();
484        let oldest_file_revision = get_oldest_file_revision(&test_drive_file).unwrap();
485
486        let response = get_resource().get(
487            &test_drive_file.clone().id.unwrap(),
488            &oldest_file_revision.clone().id.unwrap(),
489            )
490            .fields("*")
491            .execute();
492
493        assert!( response.is_ok() );
494
495        let revision = response.unwrap();
496        assert_eq!(revision, oldest_file_revision);
497
498        delete_file(&test_drive_file).expect("Failed to cleanup created file");
499    }
500
501    #[test]
502    fn get_invalid_test() {
503        let response = get_invalid_resource().get("invalid-id", "invalid-id")
504            .execute();
505
506        assert!( response.is_err() );
507        assert_eq!( response.unwrap_err().kind, ErrorKind::Response );
508    }
509
510    #[test]
511    fn get_media_test() {
512        // Only run if no other tests are using the local storage
513        let _unused = LOCAL_STORAGE_IN_USE.lock().unwrap();
514
515        let test_drive_file = get_test_drive_file().unwrap();
516        let oldest_file_revision = get_oldest_file_revision(&test_drive_file).unwrap();
517
518        let save_path = PathBuf::from("saved.txt");
519
520        let response = get_resource().get_media(
521            &test_drive_file.clone().id.unwrap(),
522            &oldest_file_revision.clone().id.unwrap(),
523            )
524            .save_to(&save_path)
525            .execute();
526
527        assert!( response.is_ok() );
528
529        let content = String::from_utf8( response.unwrap() ).unwrap();
530        let saved_content = fs::read_to_string(&save_path).unwrap();
531
532        assert_eq!(&content, "content");
533        assert_eq!(&saved_content, "content");
534
535        delete_file(&test_drive_file).expect("Failed to cleanup a created file");
536        fs::remove_file(&save_path).expect("Failed to cleanup a created file");
537    }
538
539    #[test]
540    fn get_media_invalid_test() {
541        let response = get_invalid_resource().get_media("invalid-id", "invalid-id")
542            .execute();
543
544        assert!( response.is_err() );
545        assert_eq!( response.unwrap_err().kind, ErrorKind::Response );
546    }
547
548    #[test]
549    fn list_test() {
550        let test_drive_file = get_test_drive_file().unwrap();
551
552        let response = get_resource().list( &test_drive_file.clone().id.unwrap() )
553            .execute();
554
555        assert!( response.is_ok() );
556
557        delete_file(&test_drive_file).expect("Failed to cleanup created file");
558    }
559
560    #[test]
561    fn list_invalid_test() {
562        let response = get_invalid_resource().list("invalid-id")
563            .execute();
564
565        assert!( response.is_err() );
566        assert_eq!( response.unwrap_err().kind, ErrorKind::Response );
567    }
568
569    #[test]
570    fn update_test() {
571        let test_drive_file = get_test_drive_file().unwrap();
572        let oldest_file_revision = get_oldest_file_revision(&test_drive_file).unwrap();
573
574        let updated_revision = objects::Revision {
575            published: Some(false),
576            ..Default::default()
577        };
578
579        let response = get_resource().update(
580                &test_drive_file.clone().id.unwrap(),
581                &oldest_file_revision.clone().id.unwrap(),
582            )
583            .fields("*")
584            .revision(&updated_revision)
585            .execute();
586
587        assert!( response.is_ok() );
588
589        let revision = response.unwrap();
590        assert_eq!(revision.published, updated_revision.published);
591
592        delete_file(&test_drive_file).expect("Failed to cleanup created file");
593    }
594
595    #[test]
596    fn update_invalid_test() {
597        let response = get_invalid_resource().update("invalid-id", "invalid-id")
598            .execute();
599
600        assert!( response.is_err() );
601        assert_eq!( response.unwrap_err().kind, ErrorKind::Response );
602    }
603}