tinify/sync/
source.rs

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  /// Resize the current compressed image.
148  pub fn resize(mut self, resize: Resize) -> Result<Self, TinifyError> {
149    self.operations.resize = Some(resize);
150    Ok(self)
151  }
152
153  /// Convert the current compressed image.
154  pub fn convert(mut self, convert: Convert) -> Result<Self, TinifyError> {
155    self.operations.convert = Some(convert);
156    Ok(self)
157  }
158
159  /// Transform the current compressed image.
160  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  /// Save the current compressed image to a file.
211  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  /// Save the current compressed image to a buffer.
233  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}