From 2ac0735589abc0d4aacfab4a5197da20972bebbf Mon Sep 17 00:00:00 2001 From: jakka Date: Sat, 13 Sep 2025 22:40:54 +0300 Subject: continued working on api --- Cargo.lock | 8 ++-- src/client.rs | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++------- src/tests.rs | 8 ++-- src/types.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 238 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34d785e..2b37f1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.36" +version = "1.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" +checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" dependencies = [ "find-msvc-tools", "shlex", @@ -341,9 +341,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8" dependencies = [ "ring", "rustls-pki-types", 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 = std::result::Result; @@ -80,27 +84,128 @@ impl HydrusClient { Ok(data) } + fn set_get_api_key( + &self, + request: RequestBuilder, + ) -> Result> { + 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, + ) -> Result> { + 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 { 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 { + 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> { + 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, + domains: Option, + ) -> Result { + 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 { + 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, #[musli(default)] - pub min_stars: u8, + pub min_stars: Option, #[musli(default)] - pub max_stars: u8, + pub max_stars: Option, +} + +#[derive(Decode, Debug)] +pub struct ServiceResponse { + services: HashMap, +} + +impl ServiceResponse { + pub fn service_vec(mut self) -> Vec { + 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), + DeletedFileServiceKey(String), + DeletedFileServiceKeys(Vec), +} + +#[derive(Encode, Debug, Default)] +pub struct AddFileRequest { + pub path: String, + #[musli(skip)] + pub delete_after_successful_import: Option, + #[musli(skip)] + pub file_service_key: Option, + #[musli(skip)] + pub file_service_keys: Option>, + #[musli(skip)] + pub deleted_file_service_key: Option, + #[musli(skip)] + pub deleted_file_service_keys: Option>, +} + +#[derive(PartialEq, Debug, Clone, FromRepr)] +#[repr(u8)] +pub enum FileAddStatus { + SuccessfulImport = 1, + AlreadyInDatabase, + PreviouslyDeleted, + FailedToImport, + FileVetoed = 7, +} + +impl Encode for FileAddStatus { + type Encode = Self; + + #[inline] + fn encode(&self, encoder: E) -> Result<(), E::Error> + where + E: Encoder, + { + 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(decoder: D) -> Result + 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, + #[musli(skip)] + pub file_service_keys: Option>, + #[musli(skip)] + pub deleted_file_service_key: Option, + #[musli(skip)] + pub deleted_file_service_keys: Option>, + #[musli(skip)] + pub reason: String, } -- cgit v1.3.1