diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/client.rs | 135 | ||||
| -rw-r--r-- | src/tests.rs | 8 | ||||
| -rw-r--r-- | src/types.rs | 125 |
3 files changed, 234 insertions, 34 deletions
diff --git a/src/client.rs b/src/client.rs index 49d903e..293ef2f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,7 @@ +use std::fs::File; + +use ureq::RequestBuilder; + use crate::types::*; type Result<T> = std::result::Result<T, HydrusError>; @@ -80,27 +84,128 @@ impl HydrusClient { Ok(data) } + fn set_get_api_key( + &self, + request: RequestBuilder<ureq::typestate::WithoutBody>, + ) -> Result<RequestBuilder<ureq::typestate::WithoutBody>> { + if let Some(key) = &self.sessionkey { + Ok(request.header("Hydrus-Client-API-Access-Key", key)) + } else if let Some(key) = &self.apikey { + Ok(request.header("Hydrus-Client-API-Access-Key", key)) + } else { + Err(HydrusError::KeyNotSupplied) + } + } + + fn set_post_api_key( + &self, + request: RequestBuilder<ureq::typestate::WithBody>, + ) -> Result<RequestBuilder<ureq::typestate::WithBody>> { + if let Some(key) = &self.sessionkey { + Ok(request.header("Hydrus-Client-API-Access-Key", key)) + } else if let Some(key) = &self.apikey { + Ok(request.header("Hydrus-Client-API-Access-Key", key)) + } else { + Err(HydrusError::KeyNotSupplied) + } + } + pub fn get_service_name(&self, name: &str) -> Result<Service> { let mut req_url = self.url.to_owned(); req_url.push_str("get_service?service_name="); req_url.push_str(&urlencoding::encode(name)); - let response = if let Some(key) = &self.sessionkey { - ureq::get(req_url) - .header("Hydrus-Client-API-Access-Key", key) - .call()? - .body_mut() - .read_to_vec()? - } else if let Some(key) = &self.apikey { - ureq::get(req_url) - .header("Hydrus-Client-API-Access-Key", key) - .call()? - .body_mut() - .read_to_vec()? - } else { - return Err(HydrusError::KeyNotSupplied); - }; + + let response = self + .set_get_api_key(ureq::get(req_url))? + .call()? + .body_mut() + .read_to_vec()?; + + let service: Service = musli::json::decode(response.as_slice())?; + Ok(service) + } + + pub fn get_service_key(&self, key: &str) -> Result<Service> { + let mut req_url = self.url.to_owned(); + req_url.push_str("get_service?service_key="); + req_url.push_str(&urlencoding::encode(key)); + + let response = self + .set_get_api_key(ureq::get(req_url))? + .call()? + .body_mut() + .read_to_vec()?; let service: Service = musli::json::decode(response.as_slice())?; Ok(service) } + + pub fn get_services(&self) -> Result<Vec<Service>> { + let mut req_url = self.url.to_owned(); + req_url.push_str("get_services"); + + let response = self + .set_get_api_key(ureq::get(req_url))? + .call()? + .body_mut() + .read_to_vec()?; + + let services: ServiceResponse = musli::json::decode(response.as_slice())?; + + Ok(services.service_vec()) + } + + pub fn add_file_via_path( + &self, + path: &str, + delete: Option<bool>, + domains: Option<FileDomain>, + ) -> Result<FileAddResponse> { + let mut form = AddFileRequest { + path: path.to_owned(), + delete_after_successful_import: delete, + ..Default::default() + }; + + if let Some(file_domains) = domains { + match file_domains { + FileDomain::FileServiceKey(key) => form.file_service_key = Some(key), + FileDomain::FileServiceKeys(keys) => form.file_service_keys = Some(keys), + FileDomain::DeletedFileServiceKey(key) => form.deleted_file_service_key = Some(key), + FileDomain::DeletedFileServiceKeys(keys) => { + form.deleted_file_service_keys = Some(keys) + } + } + } + + let data = musli::json::to_vec(&form)?; + + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/add_file"); + + let response = self + .set_post_api_key(ureq::post(req_url))? + .content_type("application/json") + .send(data)? + .body_mut() + .read_to_vec()?; + + let status: FileAddResponse = musli::json::decode(response.as_slice())?; + Ok(status) + } + + pub fn add_file_via_file(&self, file: &File) -> Result<FileAddResponse> { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/add_file"); + + let response = self + .set_post_api_key(ureq::post(req_url))? + .content_type("application/octet-stream") + .send(file)? + .body_mut() + .read_to_vec()?; + + let status: FileAddResponse = musli::json::decode(response.as_slice())?; + Ok(status) + } } diff --git a/src/tests.rs b/src/tests.rs index 3fafdbe..19d60cf 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -42,7 +42,9 @@ fn correct_hydrusservice_decode() { } #[test] -fn get_keys() { - let client: HydrusClient = HydrusClient::new("http://127.0.0.1:51251/"); - let _ = client.request_new_permissions("test client", &[]).unwrap(); +fn correct_hydrusservices_decode() { + let mut client: HydrusClient = HydrusClient::new("http://127.0.0.1:51251/"); + client.set_api_key("7ab7accf6cf12b2c6c30436cd8fe16361aee33679dbd90da279b5c22b33d622a"); + let test = client.get_services().unwrap(); + assert!(!test.is_empty()) } diff --git a/src/types.rs b/src/types.rs index ed00efb..f94dd3c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,5 @@ use musli::{Allocator, Decode, Decoder, Encode, Encoder}; +use std::collections::HashMap; use strum_macros::FromRepr; use thiserror::Error; @@ -6,7 +7,7 @@ use thiserror::Error; pub enum HydrusError { #[error("failed to connect to Hydrus")] NetworkError(ureq::Error), - #[error("failed to deserialize data")] + #[error("failed to encode/decode data")] DeserializeError(musli::json::Error), #[error("api or session key needed")] KeyNotSupplied, @@ -70,11 +71,7 @@ where where D: Decoder<'de>, { - if let Some(val) = HydrusPermissions::from_repr(decoder.decode()?) { - return Ok(val); - } else { - return Ok(Self::Null); - } + Ok(HydrusPermissions::from_repr(decoder.decode()?).unwrap()) } } @@ -147,25 +144,121 @@ where where D: Decoder<'de>, { - if let Some(val) = ServiceType::from_repr(decoder.decode()?) { - return Ok(val); - } else { - return Ok(Self::Null); - } + Ok(ServiceType::from_repr(decoder.decode()?).unwrap()) } } -#[derive(Decode)] +#[derive(Decode, Debug)] pub struct Service { pub name: String, #[musli(default)] pub service_key: String, - pub servicetype: ServiceType, + pub r#type: ServiceType, pub type_pretty: String, #[musli(default)] - pub star_shape: String, + pub star_shape: Option<String>, #[musli(default)] - pub min_stars: u8, + pub min_stars: Option<u8>, #[musli(default)] - pub max_stars: u8, + pub max_stars: Option<u8>, +} + +#[derive(Decode, Debug)] +pub struct ServiceResponse { + services: HashMap<String, Service>, +} + +impl ServiceResponse { + pub fn service_vec(mut self) -> Vec<Service> { + let mut res = Vec::new(); + for (key, mut service) in self.services.drain() { + service.service_key = key; + res.push(service); + } + res + } +} + +pub enum FileDomain { + FileServiceKey(String), + FileServiceKeys(Vec<String>), + DeletedFileServiceKey(String), + DeletedFileServiceKeys(Vec<String>), +} + +#[derive(Encode, Debug, Default)] +pub struct AddFileRequest { + pub path: String, + #[musli(skip)] + pub delete_after_successful_import: Option<bool>, + #[musli(skip)] + pub file_service_key: Option<String>, + #[musli(skip)] + pub file_service_keys: Option<Vec<String>>, + #[musli(skip)] + pub deleted_file_service_key: Option<String>, + #[musli(skip)] + pub deleted_file_service_keys: Option<Vec<String>>, +} + +#[derive(PartialEq, Debug, Clone, FromRepr)] +#[repr(u8)] +pub enum FileAddStatus { + SuccessfulImport = 1, + AlreadyInDatabase, + PreviouslyDeleted, + FailedToImport, + FileVetoed = 7, +} + +impl<M> Encode<M> for FileAddStatus { + type Encode = Self; + + #[inline] + fn encode<E>(&self, encoder: E) -> Result<(), E::Error> + where + E: Encoder<Mode = M>, + { + encoder.encode(self.clone() as u8) + } + + #[inline] + fn as_encode(&self) -> &Self::Encode { + self + } +} + +impl<'de, M, A> Decode<'de, M, A> for FileAddStatus +where + A: Allocator, +{ + #[inline] + fn decode<D>(decoder: D) -> Result<Self, D::Error> + where + D: Decoder<'de>, + { + Ok(FileAddStatus::from_repr(decoder.decode()?).unwrap()) + } +} + +#[derive(Decode)] +pub struct FileAddResponse { + pub status: FileAddStatus, + pub hash: String, + pub note: String, +} + +#[derive(Encode, Debug, Default)] +pub struct DeleteFileRequest { + pub path: String, + #[musli(skip)] + pub file_service_key: Option<String>, + #[musli(skip)] + pub file_service_keys: Option<Vec<String>>, + #[musli(skip)] + pub deleted_file_service_key: Option<String>, + #[musli(skip)] + pub deleted_file_service_keys: Option<Vec<String>>, + #[musli(skip)] + pub reason: String, } |
