changeset 28:366924ffc5c2

Support media download (e.g. Drive: Files.Export)
author Lewin Bormann <lbo@spheniscida.de>
date Sat, 17 Oct 2020 21:31:13 +0200
parents b02856cbbd8e
children a83949096306
files drive_example/src/drive_v3_types.rs generate/generate.py generate/templates.py
diffstat 3 files changed, 131 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/drive_example/src/drive_v3_types.rs	Sat Oct 17 20:43:04 2020 +0200
+++ b/drive_example/src/drive_v3_types.rs	Sat Oct 17 21:31:13 2020 +0200
@@ -3,6 +3,7 @@
 use chrono::{DateTime, Utc};
 use anyhow::{Error, Result};
 use std::collections::HashMap;
+use tokio::stream::{Stream, StreamExt};
 use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
 
 pub type TlsConnr = hyper_rustls::HttpsConnector<hyper::client::HttpConnector>;
@@ -3485,9 +3486,9 @@
   
 /// Exports a Google Doc to the requested MIME type and returns the exported content. Please note that the exported content is limited to 10MB.
 pub async fn export(
-    &mut self, params: &FilesExportParams) -> Result<()> {
-
-    let rel_path = format!("files/{fileId}/export", fileId=params.file_id);
+    &mut self, params: &FilesExportParams,  dst: &mut std::io::Write) -> Result<()> {
+
+    let rel_path = format!("files/trash", );
     let path = "https://www.googleapis.com/drive/v3/".to_string() + &rel_path;
     let mut scopes = &self.scopes;
     if scopes.is_empty() {
@@ -3513,10 +3514,12 @@
     if !resp.status().is_success() {
         return Err(anyhow::Error::new(ApiError::HTTPError(resp.status())));
     }
-    let resp_body = hyper::body::to_bytes(resp.into_body()).await?;
-    let bodystr = String::from_utf8(resp_body.to_vec())?;
-    let decoded = serde_json::from_str(&bodystr)?;
-    Ok(decoded)
+    let resp_body = resp.into_body();
+    let write_result = resp_body.map(move |chunk| { dst.write(chunk?.as_ref()); Ok(()) }).collect::<Vec<Result<()>>>().await;
+    if let Some(e) = write_result.into_iter().find(|r| r.is_err()) {
+        return e;
+    }
+    Ok(())
   }
 
   
--- a/generate/generate.py	Sat Oct 17 20:43:04 2020 +0200
+++ b/generate/generate.py	Sat Oct 17 21:31:13 2020 +0200
@@ -228,7 +228,10 @@
         # Types of the function
         in_type = method['request']['$ref'] if 'request' in method else '()'
         out_type = method['response']['$ref'] if 'response' in method else '()'
+
+        is_download = method.get('supportsMediaDownload', False) and not method.get('useMediaDownloadService', False)
         is_upload = 'mediaUpload' in method
+
         media_upload = method.get('mediaUpload', None)
         if media_upload and 'simple' in media_upload['protocols']:
             upload_path = media_upload['protocols']['simple']['path']
@@ -236,39 +239,14 @@
             upload_path = ''
         http_method = method['httpMethod']
 
-        formatted_path, required_params = resolve_parameters(method['path'])
-        data_normal = {
-            'name': snake_case(methodname),
-            'param_type': params_type_name,
-            'in_type': in_type,
-            'out_type': out_type,
-            'base_path': discdoc['baseUrl'],
-            'rel_path_expr': formatted_path,
-            'params': [{
-                'param': p,
-                'snake_param': sp
-            } for (p, sp) in parameters.items()],
-            'required_params': [{
-                'param': p,
-                'snake_param': sp
-            } for (p, sp) in required_parameters.items()],
-            'scopes': [{
-                'scope': s
-            } for s in method['scopes']],
-            'description': method.get('description', ''),
-            'http_method': http_method
-        }
-        if in_type == '()':
-            data_normal.pop('in_type')
-        method_fragments.append(chevron.render(NormalMethodTmpl, data_normal))
-
-        if is_upload:
-            data_upload = {
+        if is_download:
+            data_download = {
                 'name': snake_case(methodname),
                 'param_type': params_type_name,
+                'in_type': in_type,
                 'out_type': out_type,
-                'base_path': discdoc['rootUrl'],
-                'rel_path_expr': '"' + upload_path.lstrip('/') + '"',
+                'base_path': discdoc['baseUrl'],
+                'rel_path_expr': formatted_path,
                 'params': [{
                     'param': p,
                     'snake_param': sp
@@ -281,9 +259,61 @@
                     'scope': s
                 } for s in method['scopes']],
                 'description': method.get('description', ''),
-                'http_method': http_method,
+                'http_method': http_method
             }
-            method_fragments.append(chevron.render(UploadMethodTmpl, data_upload))
+            if in_type == '()':
+                data_download.pop('in_type')
+            method_fragments.append(chevron.render(DownloadMethodTmpl, data_download))
+
+        else:
+            formatted_path, required_params = resolve_parameters(method['path'])
+            data_normal = {
+                'name': snake_case(methodname),
+                'param_type': params_type_name,
+                'in_type': in_type,
+                'out_type': out_type,
+                'base_path': discdoc['baseUrl'],
+                'rel_path_expr': formatted_path,
+                'params': [{
+                    'param': p,
+                    'snake_param': sp
+                } for (p, sp) in parameters.items()],
+                'required_params': [{
+                    'param': p,
+                    'snake_param': sp
+                } for (p, sp) in required_parameters.items()],
+                'scopes': [{
+                    'scope': s
+                } for s in method['scopes']],
+                'description': method.get('description', ''),
+                'http_method': http_method
+            }
+            if in_type == '()':
+                data_normal.pop('in_type')
+            method_fragments.append(chevron.render(NormalMethodTmpl, data_normal))
+
+            if is_upload:
+                data_upload = {
+                    'name': snake_case(methodname),
+                    'param_type': params_type_name,
+                    'out_type': out_type,
+                    'base_path': discdoc['rootUrl'],
+                    'rel_path_expr': '"' + upload_path.lstrip('/') + '"',
+                    'params': [{
+                        'param': p,
+                        'snake_param': sp
+                    } for (p, sp) in parameters.items()],
+                    'required_params': [{
+                        'param': p,
+                        'snake_param': sp
+                    } for (p, sp) in required_parameters.items()],
+                    'scopes': [{
+                        'scope': s
+                    } for s in method['scopes']],
+                    'description': method.get('description', ''),
+                    'http_method': http_method,
+                }
+                method_fragments.append(chevron.render(UploadMethodTmpl, data_upload))
 
     return chevron.render(ServiceImplementationTmpl, {
         'service': service,
--- a/generate/templates.py	Sat Oct 17 20:43:04 2020 +0200
+++ b/generate/templates.py	Sat Oct 17 21:31:13 2020 +0200
@@ -4,6 +4,7 @@
 use chrono::{DateTime, Utc};
 use anyhow::{Error, Result};
 use std::collections::HashMap;
+use tokio::stream::{Stream, StreamExt};
 use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
 
 pub type TlsConnr = hyper_rustls::HttpsConnector<hyper::client::HttpConnector>;
@@ -167,3 +168,61 @@
     Ok(decoded)
   }
 '''
+
+# Takes:
+# name, param_type, in_type, out_type
+# base_path, rel_path_expr
+# params: [{param, snake_param}]
+# http_method
+DownloadMethodTmpl = '''
+/// {{{description}}}
+pub async fn {{{name}}}(
+    &mut self, params: &{{{param_type}}}, {{#in_type}}{{{in_type}}},{{/in_type}} dst: &mut std::io::Write) -> Result<{{out_type}}> {
+
+    let rel_path = {{{rel_path_expr}}};
+    let path = "{{{base_path}}}".to_string() + &rel_path;
+    let mut scopes = &self.scopes;
+    if scopes.is_empty() {
+        scopes = &vec![{{#scopes}}"{{{scope}}}".to_string(),
+        {{/scopes}}];
+    }
+    let tok = self.authenticator.token(&self.scopes).await?;
+    let mut url_params = format!("?oauth_token={token}&fields=*", token=tok.as_str());
+    {{#params}}
+    if let Some(ref val) = &params.{{{snake_param}}} {
+        url_params.push_str(&format!("&{{{param}}}={}",
+            percent_encode(format!("{}", val).as_bytes(), NON_ALPHANUMERIC).to_string()));
+    }
+    {{/params}}
+    {{#required_params}}
+    url_params.push_str(&format!("&{{{param}}}={}",
+        percent_encode(format!("{}", params.{{{snake_param}}}).as_bytes(), NON_ALPHANUMERIC).to_string()));
+    {{/required_params}}
+
+    let full_uri = path + &url_params;
+    let reqb = hyper::Request::builder()
+        .uri(full_uri)
+        .method("{{{http_method}}}")
+        .header("Content-Type", "application/json");
+
+    let body = hyper::Body::from("");
+    {{#in_type}}
+    let mut body_str = serde_json::to_string(req)?;
+    if body_str == "null" {
+        body_str.clear();
+    }
+    let body = hyper::Body::from(body_str);
+    {{/in_type}}
+    let request = reqb.body(body)?;
+    let resp = self.client.request(request).await?;
+    if !resp.status().is_success() {
+        return Err(anyhow::Error::new(ApiError::HTTPError(resp.status())));
+    }
+    let resp_body = resp.into_body();
+    let write_result = resp_body.map(move |chunk| { dst.write(chunk?.as_ref()); Ok(()) }).collect::<Vec<Result<()>>>().await;
+    if let Some(e) = write_result.into_iter().find(|r| r.is_err()) {
+        return e;
+    }
+    Ok(())
+  }
+'''