#![doc = include_str!("../README.md")]
use std::fmt::Display;
use std::time::Duration;
#[cfg(feature = "async")]
use reqwest::Client as AsyncClient;
pub use reqwest::Error;
#[cfg(feature = "blocking")]
use reqwest::blocking::Client as SyncClient;
use reqwest::{
StatusCode,
header::{HeaderMap, HeaderValue},
};
use serde::de::DeserializeOwned;
use crate::responses::{OctoJob, OctoPrinter, V1Info, V1Job, V1Status, V1Storage, Version};
pub mod responses;
#[cfg(feature = "async")]
pub type Async = AsyncClient;
#[cfg(feature = "blocking")]
pub type Sync = SyncClient;
pub struct PrusaClient<T> {
ip: String,
inner: T,
}
impl<T> PrusaClient<T> {
fn format_url(&self, endpoint: impl Display) -> String {
format!("http://{}/{}", self.ip, endpoint)
}
pub fn into_inner(self) -> T {
self.inner
}
}
#[cfg(feature = "async")]
impl PrusaClient<Async> {
pub fn new_async(
ip: impl Into<String>,
key: impl AsRef<str>,
timeout: Option<Duration>,
) -> Self {
let mut headers = HeaderMap::default();
headers.insert("X-API-KEY", HeaderValue::from_str(key.as_ref()).unwrap());
let timeout = timeout.unwrap_or(Duration::from_secs(1));
let client = AsyncClient::builder()
.default_headers(headers)
.timeout(timeout)
.build()
.unwrap();
Self {
ip: ip.into(),
inner: client,
}
}
async fn get<D: DeserializeOwned>(&self, url: &str) -> Result<D, Error> {
self.inner.get(url).send().await?.json::<D>().await
}
async fn put(&self, url: &str) -> Result<(), Error> {
self.inner.put(url).send().await?.error_for_status()?;
Ok(())
}
async fn delete(&self, url: &str) -> Result<(), Error> {
self.inner.delete(url).send().await?.error_for_status()?;
Ok(())
}
pub async fn version(&self) -> Result<Version, Error> {
let url = self.format_url("api/version");
println!("{}", url);
self.get(url.as_str()).await
}
pub async fn v1_info(&self) -> Result<V1Info, Error> {
let url = self.format_url("api/v1/info");
self.get(url.as_str()).await
}
pub async fn v1_status(&self) -> Result<V1Status, Error> {
let url = self.format_url("api/v1/status");
self.get(url.as_str()).await
}
pub async fn v1_storage(&self) -> Result<V1Storage, Error> {
let url = self.format_url("api/v1/storage");
self.get(url.as_str()).await
}
pub async fn v1_stop_job(&self, id: u64) -> Result<(), Error> {
let endpoint = format!("api/v1/job/{}", id);
let url = self.format_url(endpoint);
self.delete(url.as_str()).await
}
pub async fn v1_pause_job(&self) -> Result<Option<()>, Error> {
if let Some(job) = self.v1_get_job().await? {
let endpoint = format!("api/v1/job/{}/pause", job.id);
let url = self.format_url(endpoint);
return Ok(Some(self.put(url.as_str()).await?));
}
Ok(None)
}
pub async fn v1_resume_job(&self) -> Result<Option<()>, Error> {
if let Some(job) = self.v1_get_job().await? {
let endpoint = format!("api/v1/job/{}/resume", job.id);
let url = self.format_url(endpoint);
return Ok(Some(self.put(url.as_str()).await?));
}
Ok(None)
}
pub async fn v1_continue_job(&self) -> Result<Option<()>, Error> {
if let Some(job) = self.v1_get_job().await? {
let endpoint = format!("api/v1/job/{}/continue", job.id);
let url = self.format_url(endpoint);
return Ok(Some(self.put(url.as_str()).await?));
}
Ok(None)
}
pub async fn v1_delete_transfer(&self, id: u64) -> Result<(), Error> {
let endpoint = format!("api/v1/transfer/{}", id);
let url = self.format_url(endpoint);
self.delete(url.as_str()).await
}
pub async fn v1_put_file(
&self,
path: &str,
file: &[u8],
print_after_upload: bool,
overwrite: bool,
) -> Result<(), Error> {
let endpoint = format!("api/v1/files/usb/{}", path);
let url = self.format_url(endpoint);
let mut pau: &str = "?1";
if !print_after_upload {
pau = "?0";
}
let mut o = "?1";
if !overwrite {
o = "?0";
}
self.inner
.put(url)
.header("Content-Type", "application/octet-stream")
.header("Print-After-Upload", pau)
.header("Overwrite", o)
.body(file.to_owned())
.send()
.await?
.error_for_status()?;
Ok(())
}
pub async fn v1_start_job(&self, path: &str) -> Result<(), Error> {
let endpoint = format!("api/v1/files/usb/{}", path);
let url = self.format_url(endpoint);
self.inner.post(url).send().await?.error_for_status()?;
Ok(())
}
pub async fn v1_is_path(&self, path: &str) -> Result<(), Error> {
let endpoint = format!("api/v1/files/usb/{}", path);
let url = self.format_url(endpoint);
self.inner.head(url).send().await?.error_for_status()?;
Ok(())
}
pub async fn v1_delete_path(&self, path: &str) -> Result<(), Error> {
let endpoint = format!("api/v1/files/usb/{}", path);
let url = self.format_url(endpoint);
self.delete(url.as_str()).await
}
pub async fn v1_get_job(&self) -> Result<Option<V1Job>, Error> {
let url = self.format_url("api/v1/job");
let r = self.inner.get(url).send().await?.error_for_status()?;
if r.status() == StatusCode::NO_CONTENT {
return Ok(None);
}
let json = r.json::<V1Job>().await?;
Ok(Some(json))
}
pub async fn octo_settings(&self) -> Result<(), Error> {
let url = self.format_url("api/settings");
self.inner.get(url).send().await?.error_for_status()?;
Ok(())
}
pub async fn octo_printer(&self) -> Result<OctoPrinter, Error> {
let url = self.format_url("api/printer");
self.get(url.as_str()).await
}
pub async fn octo_job(&self) -> Result<OctoJob, Error> {
let url = self.format_url("api/job");
self.get(url.as_str()).await
}
}
#[cfg(feature = "blocking")]
impl PrusaClient<Sync> {
pub fn new_sync(
ip: impl Into<String>,
key: impl AsRef<str>,
timeout: Option<Duration>,
) -> Self {
let mut headers = HeaderMap::default();
headers.insert("X-API-KEY", HeaderValue::from_str(key.as_ref()).unwrap());
let timeout = timeout.unwrap_or(Duration::from_secs(1));
let client = SyncClient::builder()
.default_headers(headers)
.timeout(timeout)
.build()
.unwrap();
Self {
ip: ip.into(),
inner: client,
}
}
fn get<D: DeserializeOwned>(&self, url: &str) -> Result<D, Error> {
self.inner.get(url).send()?.json::<D>()
}
fn put(&self, url: &str) -> Result<(), Error> {
self.inner.put(url).send()?.error_for_status()?;
Ok(())
}
fn delete(&self, url: &str) -> Result<(), Error> {
self.inner.delete(url).send()?.error_for_status()?;
Ok(())
}
pub fn version(&self) -> Result<Version, Error> {
let url = self.format_url("api/version");
println!("{}", url);
self.get(url.as_str())
}
pub fn v1_info(&self) -> Result<V1Info, Error> {
let url = self.format_url("api/v1/info");
self.get(url.as_str())
}
pub fn v1_status(&self) -> Result<V1Status, Error> {
let url = self.format_url("api/v1/status");
self.get(url.as_str())
}
pub fn v1_storage(&self) -> Result<V1Storage, Error> {
let url = self.format_url("api/v1/storage");
self.get(url.as_str())
}
pub fn v1_stop_job(&self) -> Result<Option<()>, Error> {
if let Some(job) = self.v1_get_job()? {
let endpoint = format!("api/v1/job/{}", job.id);
let url = self.format_url(endpoint);
return Ok(Some(self.delete(url.as_str())?));
}
Ok(None)
}
pub fn v1_pause_job(&self) -> Result<Option<()>, Error> {
if let Some(job) = self.v1_get_job()? {
let endpoint = format!("api/v1/job/{}/pause", job.id);
let url = self.format_url(endpoint);
return Ok(Some(self.put(url.as_str())?));
}
Ok(None)
}
pub fn v1_resume_job(&self) -> Result<Option<()>, Error> {
if let Some(job) = self.v1_get_job()? {
let endpoint = format!("api/v1/job/{}/resume", job.id);
let url = self.format_url(endpoint);
return Ok(Some(self.put(url.as_str())?));
}
Ok(None)
}
pub fn v1_continue_job(&self) -> Result<Option<()>, Error> {
if let Some(job) = self.v1_get_job()? {
let endpoint = format!("api/v1/job/{}/continue", job.id);
let url = self.format_url(endpoint);
return Ok(Some(self.put(url.as_str())?));
}
Ok(None)
}
pub fn v1_delete_transfer(&self, id: u64) -> Result<(), Error> {
let endpoint = format!("api/v1/transfer/{}", id);
let url = self.format_url(endpoint);
self.delete(url.as_str())
}
pub fn v1_put_file(
&self,
path: &str,
file: &[u8],
print_after_upload: bool,
overwrite: bool,
) -> Result<(), Error> {
let endpoint = format!("api/v1/files/usb/{}", path);
let url = self.format_url(endpoint);
let mut pau: &str = "?1";
if !print_after_upload {
pau = "?0";
}
let mut o = "?1";
if !overwrite {
o = "?0";
}
self.inner
.put(url)
.header("Content-Type", "application/octet-stream")
.header("Print-After-Upload", pau)
.header("Overwrite", o)
.body(file.to_owned())
.send()?
.error_for_status()?;
Ok(())
}
pub fn v1_is_path(&self, path: &str) -> Result<(), Error> {
let endpoint = format!("api/v1/files/usb/{}", path);
let url = self.format_url(endpoint);
self.inner.post(url).send()?.error_for_status()?;
Ok(())
}
pub fn v1_path_exists(&self, path: &str) -> Result<(), Error> {
let endpoint = format!("api/v1/files/usb/{}", path);
let url = self.format_url(endpoint);
self.inner.head(url).send()?.error_for_status()?;
Ok(())
}
pub fn v1_delete_path(&self, path: &str) -> Result<(), Error> {
let endpoint = format!("api/v1/files/usb/{}", path);
let url = self.format_url(endpoint);
self.delete(url.as_str())
}
pub fn v1_get_job(&self) -> Result<Option<V1Job>, Error> {
let url = self.format_url("api/v1/job");
let r = self.inner.get(url).send()?.error_for_status()?;
if r.status() == StatusCode::NO_CONTENT {
return Ok(None);
}
let json = r.json::<V1Job>()?;
Ok(Some(json))
}
pub fn get_octo_settings(&self) -> Result<(), Error> {
let url = self.format_url("api/settings");
self.inner.get(url).send()?.error_for_status()?;
Ok(())
}
pub fn get_octo_printer(&self) -> Result<OctoPrinter, Error> {
let url = self.format_url("api/printer");
self.get(url.as_str())
}
pub fn get_octo_job(&self) -> Result<OctoJob, Error> {
let url = self.format_url("api/job");
self.get(url.as_str())
}
}
#[cfg(test)]
mod async_test {
use dotenvy_macro::dotenv;
use crate::{Async, PrusaClient};
fn new_async_client() -> PrusaClient<Async> {
let ip = dotenv!("PRUSA_IP");
let key = dotenv!("PRUSA_KEY");
PrusaClient::new_async(ip, key, None)
}
#[tokio::test]
async fn test_version() {
let client = new_async_client();
let status = client.version().await;
println!("{:?}", status);
assert!(status.is_ok())
}
}
#[cfg(feature = "blocking")]
#[cfg(test)]
mod sync_test {
use dotenvy_macro::dotenv;
use crate::{PrusaClient, Sync};
fn new_sync_client() -> PrusaClient<Sync> {
let ip = dotenv!("PRUSA_IP");
let key = dotenv!("PRUSA_KEY");
PrusaClient::new_sync(ip, key, None)
}
#[test]
fn test_version() {
let client = new_sync_client();
let status = client.version();
println!("{:?}", status);
assert!(status.is_ok())
}
}