ffsend_api/action/
info.rs

1use std::cmp::max;
2
3use reqwest::Error as ReqwestError;
4use thiserror::Error;
5
6use crate::api::data::{Error as DataError, OwnedData};
7use crate::api::nonce::{request_nonce, NonceError};
8use crate::api::request::{ensure_success, ResponseError};
9use crate::api::url::UrlBuilder;
10use crate::client::Client;
11use crate::file::remote_file::RemoteFile;
12
13/// An action to fetch info of a shared file.
14///
15/// This API specification for this action is compatible with both Firefox Send v2 and v3.
16pub struct Info<'a> {
17    /// The remote file to fetch the info for.
18    file: &'a RemoteFile,
19
20    /// The authentication nonce.
21    /// May be an empty vector if the nonce is unknown.
22    nonce: Vec<u8>,
23}
24
25impl<'a> Info<'a> {
26    /// Construct a new info action for the given remote file.
27    pub fn new(file: &'a RemoteFile, nonce: Option<Vec<u8>>) -> Self {
28        Self {
29            file,
30            nonce: nonce.unwrap_or_default(),
31        }
32    }
33
34    /// Invoke the info action.
35    pub fn invoke(mut self, client: &Client) -> Result<InfoResponse, Error> {
36        // Fetch the authentication nonce if not set yet
37        if self.nonce.is_empty() {
38            self.nonce = self.fetch_auth_nonce(client)?;
39        }
40
41        // Create owned data, to send to the server for authentication
42        let data = OwnedData::from(InfoData::new(), &self.file)
43            .map_err(|err| -> PrepareError { err.into() })?;
44
45        // Send the info request
46        self.fetch_info(client, &data)
47    }
48
49    /// Fetch the authentication nonce for the file from the remote server.
50    fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, Error> {
51        request_nonce(client, UrlBuilder::download(self.file, false)).map_err(|err| err.into())
52    }
53
54    /// Send the request for fetching the remote file info.
55    fn fetch_info(
56        &self,
57        client: &Client,
58        data: &OwnedData<InfoData>,
59    ) -> Result<InfoResponse, Error> {
60        // Get the info URL, and send the request
61        let url = UrlBuilder::api_info(self.file);
62        let response = client
63            .post(url)
64            .json(&data)
65            .send()
66            .map_err(|_| InfoError::Request)?;
67
68        // Ensure the response is successful
69        ensure_success(&response)?;
70
71        // Decode the JSON response
72        let response: InfoResponse = match response.json() {
73            Ok(response) => response,
74            Err(err) => return Err(InfoError::Decode(err).into()),
75        };
76
77        Ok(response)
78    }
79}
80
81/// The info data object.
82/// This object is currently empty, as no additional data is sent to the
83/// server.
84#[derive(Debug, Serialize, Default)]
85pub struct InfoData {}
86
87impl InfoData {
88    /// Constructor.
89    pub fn new() -> Self {
90        InfoData::default()
91    }
92}
93
94/// The file info response.
95#[derive(Debug, Deserialize)]
96pub struct InfoResponse {
97    /// The download limit.
98    #[serde(rename = "dlimit")]
99    download_limit: usize,
100
101    /// The total number of times the file has been downloaded.
102    #[serde(rename = "dtotal")]
103    download_count: usize,
104
105    /// The time to live for this file in milliseconds.
106    #[serde(rename = "ttl")]
107    ttl: u64,
108}
109
110impl InfoResponse {
111    /// Get the number of times this file has been downloaded.
112    pub fn download_count(&self) -> usize {
113        self.download_count
114    }
115
116    /// Get the maximum number of times the file may be downloaded.
117    pub fn download_limit(&self) -> usize {
118        self.download_limit
119    }
120
121    /// Get the number of times this file may still be downloaded.
122    pub fn download_left(&self) -> usize {
123        max(self.download_limit() - self.download_count(), 0)
124    }
125
126    /// Get the time to live for this file, in milliseconds from the time the
127    /// request was made.
128    pub fn ttl_millis(&self) -> u64 {
129        self.ttl
130    }
131}
132
133#[derive(Error, Debug)]
134pub enum Error {
135    /// An error occurred while preparing the action.
136    #[error("failed to prepare the action")]
137    Prepare(#[from] PrepareError),
138
139    /// The given Send file has expired, or did never exist in the first place.
140    /// Therefore the file could not be downloaded.
141    #[error("the file has expired or did never exist")]
142    Expired,
143
144    /// An error has occurred while sending the info request to the server.
145    #[error("failed to send the file info request")]
146    Info(#[from] InfoError),
147}
148
149impl From<NonceError> for Error {
150    fn from(err: NonceError) -> Error {
151        match err {
152            NonceError::Expired => Error::Expired,
153            err => Error::Prepare(PrepareError::Auth(err)),
154        }
155    }
156}
157
158impl From<ResponseError> for Error {
159    fn from(err: ResponseError) -> Error {
160        match err {
161            ResponseError::Expired => Error::Expired,
162            err => Error::Info(InfoError::Response(err)),
163        }
164    }
165}
166
167#[derive(Debug, Error)]
168pub enum InfoDataError {
169    /// Some error occurred while trying to wrap the info data in an
170    /// owned object, which is required for authentication on the server.
171    /// The wrapped error further described the problem.
172    #[error("")]
173    Owned(#[from] DataError),
174}
175
176#[derive(Error, Debug)]
177pub enum PrepareError {
178    /// Failed authenticating, needed to fetch the info
179    #[error("failed to authenticate")]
180    Auth(#[from] NonceError),
181
182    /// An error occurred while building the info data that will be
183    /// send to the server.
184    #[error("invalid parameters")]
185    InfoData(#[from] InfoDataError),
186}
187
188impl From<DataError> for PrepareError {
189    fn from(err: DataError) -> PrepareError {
190        PrepareError::InfoData(InfoDataError::Owned(err))
191    }
192}
193
194#[derive(Error, Debug)]
195pub enum InfoError {
196    /// Sending the request to fetch the file info failed.
197    #[error("failed to send file info request")]
198    Request,
199
200    /// The server responded with an error while fetching the file info.
201    #[error("bad response from server while fetching file info")]
202    Response(#[from] ResponseError),
203
204    /// Failed to decode the info response from the server.
205    /// Maybe the server responded with data from a newer API version.
206    #[error("failed to decode info response")]
207    Decode(#[from] ReqwestError),
208}