use std::sync::Arc;
use std::time::{Duration, Instant};
use serde_json::{json, Map, Value};
use crate::client::Input;
use crate::data;
use crate::errors::{Api2ConvertError, Result};
use crate::models::{InputFile, Job, OutputFile, Preset};
use crate::transport::{encode_segment, Transport};
use crate::upload;
const MIN_POLL_INTERVAL: Duration = Duration::from_millis(500);
const MAX_POLL_TIMEOUT: Duration = Duration::from_secs(14400);
pub struct JobsResource {
transport: Arc<Transport>,
}
impl JobsResource {
pub(crate) fn new(transport: Arc<Transport>) -> Self {
JobsResource { transport }
}
pub fn create(&self, payload: Value, idempotency_key: Option<&str>) -> Result<Job> {
let v =
self.transport
.account_request("POST", "jobs", &[], Some(payload), idempotency_key)?;
Ok(Job::from_value(&v))
}
pub fn get(&self, job_id: &str) -> Result<Job> {
let path = format!("jobs/{}", encode_segment(job_id));
let v = self
.transport
.account_request("GET", &path, &[], None, None)?;
Ok(Job::from_value(&v))
}
pub fn list(&self, status: Option<&str>, page: Option<u32>) -> Result<Vec<Job>> {
let mut query: Vec<(&str, String)> = vec![("page", page.unwrap_or(1).max(1).to_string())];
if let Some(s) = status {
if !s.is_empty() {
query.push(("status", s.to_string()));
}
}
let v = self
.transport
.account_request("GET", "jobs", &query, None, None)?;
Ok(data::map_objects(Some(&v), Job::from_value))
}
pub fn update(&self, job_id: &str, payload: Value) -> Result<Job> {
let path = format!("jobs/{}", encode_segment(job_id));
let v = self
.transport
.account_request("PATCH", &path, &[], Some(payload), None)?;
Ok(Job::from_value(&v))
}
pub fn start(&self, job_id: &str) -> Result<Job> {
self.update(job_id, json!({ "process": true }))
}
pub fn cancel(&self, job_id: &str) -> Result<()> {
let path = format!("jobs/{}", encode_segment(job_id));
self.transport
.account_request("DELETE", &path, &[], None, None)?;
Ok(())
}
pub fn add_input(&self, job_id: &str, descriptor: Value) -> Result<InputFile> {
let path = format!("jobs/{}/input", encode_segment(job_id));
let v = self
.transport
.account_request("POST", &path, &[], Some(descriptor), None)?;
Ok(InputFile::from_value(&v))
}
pub fn upload(
&self,
job: &Job,
file: impl Into<Input>,
filename: Option<&str>,
) -> Result<InputFile> {
upload::upload(&self.transport, job, file.into(), filename)
}
pub fn outputs(&self, job_id: &str) -> Result<Vec<OutputFile>> {
let path = format!("jobs/{}/output", encode_segment(job_id));
let v = self
.transport
.account_request("GET", &path, &[], None, None)?;
Ok(data::map_objects(Some(&v), OutputFile::from_value))
}
pub fn wait(
&self,
job_id: &str,
timeout: Option<Duration>,
throw_on_failure: bool,
) -> Result<Job> {
let timeout = timeout
.unwrap_or_else(|| self.transport.poll_timeout())
.min(MAX_POLL_TIMEOUT);
let max_interval = self.transport.poll_max_interval().max(MIN_POLL_INTERVAL);
let mut interval = self.transport.poll_interval().max(MIN_POLL_INTERVAL);
let deadline = Instant::now() + timeout;
loop {
let job = self.get(job_id)?;
if throw_on_failure && (job.is_failed() || job.is_canceled()) {
return Err(Api2ConvertError::ConversionFailed {
errors: job.errors.clone(),
job: Box::new(job),
});
}
if job.is_terminal() {
return Ok(job);
}
if Instant::now() >= deadline {
return Err(Api2ConvertError::ConversionTimeout { job: Box::new(job) });
}
self.transport.poll_pause(interval);
interval = max_interval.min(interval.mul_f64(1.5));
}
}
}
pub struct ConversionsResource {
transport: Arc<Transport>,
}
impl ConversionsResource {
pub(crate) fn new(transport: Arc<Transport>) -> Self {
ConversionsResource { transport }
}
pub fn list(
&self,
category: Option<&str>,
target: Option<&str>,
page: Option<u32>,
) -> Result<Vec<Value>> {
let mut query: Vec<(&str, String)> = vec![("page", page.unwrap_or(1).max(1).to_string())];
if let Some(c) = category {
if !c.is_empty() {
query.push(("category", c.to_string()));
}
}
if let Some(t) = target {
if !t.is_empty() {
query.push(("target", t.to_string()));
}
}
let v = self
.transport
.account_request("GET", "conversions", &query, None, None)?;
Ok(data::list(Some(&v)))
}
pub fn options(&self, target: &str, category: Option<&str>) -> Result<Map<String, Value>> {
let rows = self.list(category, Some(target), None)?;
match rows.first() {
Some(row) => Ok(data::object(row.get("options"))),
None => Ok(Map::new()),
}
}
}
pub struct PresetsResource {
transport: Arc<Transport>,
}
impl PresetsResource {
pub(crate) fn new(transport: Arc<Transport>) -> Self {
PresetsResource { transport }
}
pub fn list(
&self,
category: Option<&str>,
target: Option<&str>,
filter: Option<&str>,
) -> Result<Vec<Preset>> {
let mut query: Vec<(&str, String)> = Vec::new();
if let Some(c) = category {
if !c.is_empty() {
query.push(("category", c.to_string()));
}
}
if let Some(t) = target {
if !t.is_empty() {
query.push(("target", t.to_string()));
}
}
if let Some(fl) = filter {
if !fl.is_empty() {
query.push(("filter", fl.to_string()));
}
}
let v = self
.transport
.account_request("GET", "presets", &query, None, None)?;
Ok(data::map_objects(Some(&v), Preset::from_value))
}
pub fn create(&self, payload: Value) -> Result<Preset> {
let v = self
.transport
.account_request("POST", "presets", &[], Some(payload), None)?;
Ok(Preset::from_value(&v))
}
pub fn get(&self, preset_id: &str) -> Result<Preset> {
let path = format!("presets/{}", encode_segment(preset_id));
let v = self
.transport
.account_request("GET", &path, &[], None, None)?;
Ok(Preset::from_value(&v))
}
pub fn update(&self, preset_id: &str, payload: Value) -> Result<Preset> {
let path = format!("presets/{}", encode_segment(preset_id));
let v = self
.transport
.account_request("PATCH", &path, &[], Some(payload), None)?;
Ok(Preset::from_value(&v))
}
pub fn delete(&self, preset_id: &str) -> Result<()> {
let path = format!("presets/{}", encode_segment(preset_id));
self.transport
.account_request("DELETE", &path, &[], None, None)?;
Ok(())
}
}
pub struct StatsResource {
transport: Arc<Transport>,
}
impl StatsResource {
pub(crate) fn new(transport: Arc<Transport>) -> Self {
StatsResource { transport }
}
fn fetch(&self, period: &str, value: &str, filter: Option<&str>) -> Result<Value> {
let filter = filter.filter(|f| !f.is_empty()).unwrap_or("all");
let path = format!(
"stats/{}/{}/{}",
encode_segment(period),
encode_segment(value),
encode_segment(filter)
);
self.transport
.account_request("GET", &path, &[], None, None)
}
pub fn day(&self, day: &str, filter: Option<&str>) -> Result<Value> {
self.fetch("day", day, filter)
}
pub fn month(&self, month: &str, filter: Option<&str>) -> Result<Value> {
self.fetch("month", month, filter)
}
pub fn year(&self, year: &str, filter: Option<&str>) -> Result<Value> {
self.fetch("year", year, filter)
}
}
pub struct ContractsResource {
transport: Arc<Transport>,
}
impl ContractsResource {
pub(crate) fn new(transport: Arc<Transport>) -> Self {
ContractsResource { transport }
}
pub fn get(&self) -> Result<Value> {
self.transport
.account_request("GET", "contracts", &[], None, None)
}
}