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}