1use std::io::Read;
2
3use crate::{upload_file, Error, Result};
4
5use super::{Client, Endpoint};
6
7impl Client {
8 pub fn encode_filename(&self, file_path: &str) -> Result<String> {
10 use base64::{Base64, Encoding};
11
12 let path = std::path::Path::new(file_path);
13
14 path.file_name()
15 .and_then(|s| s.to_str())
16 .map(|s| Base64::encode_string(s.as_bytes()))
17 .ok_or(Error::io_filename("missing file name"))
18 }
19
20 pub async fn tus_create(&self, file_name: &str, file_len: usize) -> Result<String> {
24 let file_md = self
25 .encode_filename(file_name)
26 .map(|b64| format!("filename {b64},is_confidential"))?;
27
28 self.http
29 .post(self.endpoint(Endpoint::ResumableUploadFile))
30 .header("Authorization", self.auth_header())
31 .header("Content-Length", "0")
32 .header("Upload-Length", format!("{file_len}"))
33 .header("Tus-Resumable", "1.0.0")
34 .header("Upload-Metadata", file_md)
35 .send()
36 .await
37 .map_err(Error::from)
38 .and_then(|r| {
39 r.headers()
40 .get("Location")
41 .cloned()
42 .ok_or(Error::io_other("missing Location header"))
43 })
44 .and_then(|v| v.to_str().map(|s| s.to_string()).map_err(Error::from))
45 }
46
47 pub async fn tus_upload(
51 &self,
52 file_url: &str,
53 file_path: &str,
54 ) -> Result<upload_file::Response> {
55 let file = std::fs::File::open(file_path)?;
56 let file_len = file
57 .metadata()
58 .map_err(Error::from)
59 .map(|m| m.len() as usize)?;
60
61 let mut file_buf = std::io::BufReader::new(file);
62
63 let mut body = Vec::with_capacity(file_len);
64 file_buf.read_to_end(&mut body)?;
65
66 let res = self
67 .http
68 .patch(file_url)
69 .header("Authorization", self.auth_header())
70 .header("Content-Type", "application/offset+octet-stream")
71 .header("Upload-Offset", "0")
72 .header("Tus-Resumable", "1.0.0")
73 .body(body)
74 .send()
75 .await?;
76
77 log::debug!("File upload (TUS) response: {res:?}");
78
79 let bytes = res.bytes().await?;
80 let body = str::from_utf8(&bytes[..])?;
81
82 log::debug!("File upload (TUS) response body: {body}");
83
84 serde_json::from_str::<upload_file::Response>(body).map_err(Error::from)
85 }
86
87 pub async fn tus_create_and_upload(&self, file_path: &str) -> Result<upload_file::Response> {
89 let file = std::fs::File::open(file_path)?;
90 let path = std::path::Path::new(file_path);
91
92 let file_name = path
93 .file_name()
94 .and_then(|p| p.to_str())
95 .ok_or(Error::io_other("invalid filename for the file path"))?;
96
97 let file_md = self
98 .encode_filename(file_name)
99 .map(|b64| format!("filename {b64},is_confidential"))?;
100
101 let file_len = file
102 .metadata()
103 .map_err(Error::from)
104 .map(|m| m.len() as usize)?;
105
106 let mut file_buf = std::io::BufReader::new(file);
107
108 let mut body = Vec::with_capacity(file_len);
109 file_buf.read_to_end(&mut body)?;
110
111 let res = self
112 .http
113 .post(self.endpoint(Endpoint::ResumableUploadFile))
114 .header("Authorization", self.auth_header())
115 .header("Content-Type", "application/offset+octet-stream")
116 .header("Upload-Length", format!("{file_len}"))
117 .header("Upload-Offset", "0")
118 .header("Tus-Resumable", "1.0.0")
119 .header("Upload-Metadata", file_md)
120 .body(body)
121 .send()
122 .await?;
123
124 log::debug!("File upload (TUS) response: {res:?}");
125
126 let bytes = res.bytes().await?;
127 let body = str::from_utf8(&bytes[..])?;
128
129 log::debug!("File upload (TUS) response body: {body}");
130
131 serde_json::from_str::<upload_file::Response>(body).map_err(Error::from)
132 }
133}