use std::cmp::max;
use reqwest::Error as ReqwestError;
use thiserror::Error;
use crate::api::data::{Error as DataError, OwnedData};
use crate::api::nonce::{request_nonce, NonceError};
use crate::api::request::{ensure_success, ResponseError};
use crate::api::url::UrlBuilder;
use crate::client::Client;
use crate::file::remote_file::RemoteFile;
pub struct Info<'a> {
file: &'a RemoteFile,
nonce: Vec<u8>,
}
impl<'a> Info<'a> {
pub fn new(file: &'a RemoteFile, nonce: Option<Vec<u8>>) -> Self {
Self {
file,
nonce: nonce.unwrap_or_default(),
}
}
pub fn invoke(mut self, client: &Client) -> Result<InfoResponse, Error> {
if self.nonce.is_empty() {
self.nonce = self.fetch_auth_nonce(client)?;
}
let data = OwnedData::from(InfoData::new(), &self.file)
.map_err(|err| -> PrepareError { err.into() })?;
self.fetch_info(client, &data)
}
fn fetch_auth_nonce(&self, client: &Client) -> Result<Vec<u8>, Error> {
request_nonce(client, UrlBuilder::download(self.file, false)).map_err(|err| err.into())
}
fn fetch_info(
&self,
client: &Client,
data: &OwnedData<InfoData>,
) -> Result<InfoResponse, Error> {
let url = UrlBuilder::api_info(self.file);
let response = client
.post(url)
.json(&data)
.send()
.map_err(|_| InfoError::Request)?;
ensure_success(&response)?;
let response: InfoResponse = match response.json() {
Ok(response) => response,
Err(err) => return Err(InfoError::Decode(err).into()),
};
Ok(response)
}
}
#[derive(Debug, Serialize, Default)]
pub struct InfoData {}
impl InfoData {
pub fn new() -> Self {
InfoData::default()
}
}
#[derive(Debug, Deserialize)]
pub struct InfoResponse {
#[serde(rename = "dlimit")]
download_limit: usize,
#[serde(rename = "dtotal")]
download_count: usize,
#[serde(rename = "ttl")]
ttl: u64,
}
impl InfoResponse {
pub fn download_count(&self) -> usize {
self.download_count
}
pub fn download_limit(&self) -> usize {
self.download_limit
}
pub fn download_left(&self) -> usize {
max(self.download_limit() - self.download_count(), 0)
}
pub fn ttl_millis(&self) -> u64 {
self.ttl
}
}
#[derive(Error, Debug)]
pub enum Error {
#[error("failed to prepare the action")]
Prepare(#[from] PrepareError),
#[error("the file has expired or did never exist")]
Expired,
#[error("failed to send the file info request")]
Info(#[from] InfoError),
}
impl From<NonceError> for Error {
fn from(err: NonceError) -> Error {
match err {
NonceError::Expired => Error::Expired,
err => Error::Prepare(PrepareError::Auth(err)),
}
}
}
impl From<ResponseError> for Error {
fn from(err: ResponseError) -> Error {
match err {
ResponseError::Expired => Error::Expired,
err => Error::Info(InfoError::Response(err)),
}
}
}
#[derive(Debug, Error)]
pub enum InfoDataError {
#[error("")]
Owned(#[from] DataError),
}
#[derive(Error, Debug)]
pub enum PrepareError {
#[error("failed to authenticate")]
Auth(#[from] NonceError),
#[error("invalid parameters")]
InfoData(#[from] InfoDataError),
}
impl From<DataError> for PrepareError {
fn from(err: DataError) -> PrepareError {
PrepareError::InfoData(InfoDataError::Owned(err))
}
}
#[derive(Error, Debug)]
pub enum InfoError {
#[error("failed to send file info request")]
Request,
#[error("bad response from server while fetching file info")]
Response(#[from] ResponseError),
#[error("failed to decode info response")]
Decode(#[from] ReqwestError),
}