1use crate::convert::Convert;
2use crate::error::TinifyError;
3use crate::error::Upstream;
4use crate::resize::Resize;
5use crate::transform::Transform;
6use crate::Operations;
7use crate::SourceUrl;
8use crate::API_ENDPOINT;
9use reqwest::blocking::Client as ReqwestClient;
10use reqwest::header::HeaderValue;
11use reqwest::header::CONTENT_TYPE;
12use reqwest::StatusCode;
13use serde_json::json;
14use serde_json::Value;
15use std::fs::File;
16use std::io::BufReader;
17use std::io::BufWriter;
18use std::io::Read;
19use std::io::Write;
20use std::path::Path;
21use std::str;
22use std::time::Duration;
23use url::Url;
24
25#[derive(Debug)]
26pub struct Source {
27 key: Option<String>,
28 buffer: Option<Vec<u8>>,
29 output: Option<String>,
30 reqwest_client: ReqwestClient,
31 operations: Operations,
32}
33
34impl Source {
35 pub(crate) fn new(key: Option<&str>) -> Self {
36 let key = key.map(|val| val.into());
37 let reqwest_client = ReqwestClient::new();
38 let operations = Operations {
39 convert: None,
40 resize: None,
41 transform: None,
42 };
43
44 Self {
45 key,
46 buffer: None,
47 output: None,
48 reqwest_client,
49 operations,
50 }
51 }
52
53 fn get_source_from_response(
54 mut self,
55 buffer: Option<&[u8]>,
56 json: Option<Value>,
57 ) -> Result<Self, TinifyError> {
58 let parse = Url::parse(API_ENDPOINT)?;
59 let url = parse.join("/shrink")?;
60 let compressed_image = if let Some(json) = json {
61 self
62 .reqwest_client
63 .post(url)
64 .header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
65 .body(json.to_string())
66 .basic_auth("api", self.key.as_ref())
67 .timeout(Duration::from_secs(300))
68 .send()?
69 } else {
70 self
71 .reqwest_client
72 .post(url)
73 .body(buffer.unwrap().to_vec())
74 .basic_auth("api", self.key.as_ref())
75 .timeout(Duration::from_secs(300))
76 .send()?
77 };
78
79 match compressed_image.status() {
80 StatusCode::CREATED => {
81 if let Some(location) = compressed_image.headers().get("location") {
82 let location = location.to_str()?.to_string();
83 let bytes = self
84 .reqwest_client
85 .get(&location)
86 .timeout(Duration::from_secs(300))
87 .send()?
88 .bytes()?
89 .to_vec();
90
91 self.buffer = Some(bytes);
92 self.output = Some(location);
93
94 Ok(self)
95 } else {
96 let upstream = Upstream {
97 error: "Empty".to_string(),
98 message: "The location of the compressed image is empty."
99 .to_string(),
100 };
101 Err(TinifyError::ServerError { upstream })
102 }
103 }
104 StatusCode::UNAUTHORIZED | StatusCode::UNSUPPORTED_MEDIA_TYPE => {
105 let upstream: Upstream =
106 serde_json::from_str(&compressed_image.text()?)?;
107 Err(TinifyError::ClientError { upstream })
108 }
109 _ => {
110 let upstream: Upstream =
111 serde_json::from_str(&compressed_image.text()?)?;
112 Err(TinifyError::ServerError { upstream })
113 }
114 }
115 }
116
117 #[allow(clippy::wrong_self_convention)]
118 pub(crate) fn from_buffer(self, buffer: &[u8]) -> Result<Self, TinifyError> {
119 self.get_source_from_response(Some(buffer), None)
120 }
121
122 #[allow(clippy::wrong_self_convention)]
123 pub(crate) fn from_file<P>(self, path: P) -> Result<Self, TinifyError>
124 where
125 P: AsRef<Path>,
126 {
127 let file = File::open(path)?;
128 let mut reader = BufReader::new(file);
129 let mut buffer = Vec::with_capacity(reader.capacity());
130 reader.read_to_end(&mut buffer)?;
131
132 self.get_source_from_response(Some(&buffer), None)
133 }
134
135 #[allow(clippy::wrong_self_convention)]
136 pub(crate) fn from_url<P>(self, path: P) -> Result<Self, TinifyError>
137 where
138 P: AsRef<str> + Into<String>,
139 {
140 let json = json!({
141 "source": SourceUrl { url: path.into() },
142 });
143
144 self.get_source_from_response(None, Some(json))
145 }
146
147 pub fn resize(mut self, resize: Resize) -> Result<Self, TinifyError> {
149 self.operations.resize = Some(resize);
150 Ok(self)
151 }
152
153 pub fn convert(mut self, convert: Convert) -> Result<Self, TinifyError> {
155 self.operations.convert = Some(convert);
156 Ok(self)
157 }
158
159 pub fn transform(
161 mut self,
162 transform: Transform,
163 ) -> Result<Self, TinifyError> {
164 self.operations.transform = Some(transform);
165 Ok(self)
166 }
167
168 fn run_operations(&mut self) -> Result<(), TinifyError> {
169 let operations = serde_json::to_string(&self.operations)?;
170
171 if let Some(output) = self.output.take() {
172 let response = self
173 .reqwest_client
174 .post(output)
175 .header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
176 .body(operations)
177 .basic_auth("api", self.key.as_ref())
178 .timeout(Duration::from_secs(300))
179 .send()?;
180
181 match response.status() {
182 StatusCode::OK => {
183 let bytes = response.bytes()?.to_vec();
184
185 self.buffer = Some(bytes);
186
187 Ok(())
188 }
189 StatusCode::BAD_REQUEST
190 | StatusCode::UNAUTHORIZED
191 | StatusCode::UNSUPPORTED_MEDIA_TYPE => {
192 let upstream: Upstream = serde_json::from_str(&response.text()?)?;
193 Err(TinifyError::ClientError { upstream })
194 }
195 StatusCode::SERVICE_UNAVAILABLE => {
196 let upstream: Upstream = serde_json::from_str(&response.text()?)?;
197 Err(TinifyError::ServerError { upstream })
198 }
199 _ => unreachable!(),
200 }
201 } else {
202 let upstream = Upstream {
203 error: "Empty".to_string(),
204 message: "Output of the compressed image is empty.".to_string(),
205 };
206 Err(TinifyError::ClientError { upstream })
207 }
208 }
209
210 pub fn to_file<P>(&mut self, path: P) -> Result<(), TinifyError>
212 where
213 P: AsRef<Path>,
214 {
215 if self.operations.convert.is_some()
216 || self.operations.resize.is_some()
217 || self.operations.transform.is_some()
218 {
219 self.run_operations()?;
220 }
221
222 if let Some(ref buffer) = self.buffer {
223 let file = File::create(path)?;
224 let mut reader = BufWriter::new(file);
225 reader.write_all(buffer)?;
226 reader.flush()?;
227 }
228
229 Ok(())
230 }
231
232 pub fn to_buffer(&mut self) -> Result<Vec<u8>, TinifyError> {
234 if self.operations.convert.is_some()
235 || self.operations.resize.is_some()
236 || self.operations.transform.is_some()
237 {
238 self.run_operations()?;
239 }
240
241 if let Some(buffer) = self.buffer.as_ref() {
242 Ok(buffer.to_vec())
243 } else {
244 let upstream = Upstream {
245 error: "Empty".to_string(),
246 message: "Buffer of the compressed image is empty.".to_string(),
247 };
248 Err(TinifyError::ClientError { upstream })
249 }
250 }
251}