diff options
| author | jakka <jakka@jakka.su> | 2025-09-17 21:25:08 +0300 |
|---|---|---|
| committer | jakka <jakka@jakka.su> | 2025-09-17 21:25:08 +0300 |
| commit | a5f74e05d49ad938c3f0854dc9b1d203a570a8d9 (patch) | |
| tree | 76251488c9c05c6319999ef50f21a0ccd2fbf18a /src | |
| parent | d07a6af4563c0fde224aa630fb2d9feec01579f2 (diff) | |
moved to async http client and serde
Diffstat (limited to 'src')
| -rw-r--r-- | src/client.rs | 167 | ||||
| -rw-r--r-- | src/tests.rs | 48 | ||||
| -rw-r--r-- | src/types.rs | 166 |
3 files changed, 129 insertions, 252 deletions
diff --git a/src/client.rs b/src/client.rs index 293ef2f..c323714 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,12 +1,14 @@ -use std::fs::File; +use std::path::Path; -use ureq::RequestBuilder; +use reqwest::{Body, RequestBuilder}; +use tokio_util::codec::{BytesCodec, FramedRead}; use crate::types::*; type Result<T> = std::result::Result<T, HydrusError>; pub struct HydrusClient { + client: reqwest::Client, apikey: Option<String>, sessionkey: Option<String>, url: String, @@ -15,6 +17,7 @@ pub struct HydrusClient { impl HydrusClient { pub fn new(url: &str) -> HydrusClient { HydrusClient { + client: reqwest::Client::new(), apikey: None, sessionkey: None, url: url.to_string(), @@ -29,65 +32,68 @@ impl HydrusClient { self.sessionkey = Some(key.to_owned()) } - pub fn request_new_permissions( + pub async fn request_new_permissions( &self, name: &str, permissions: &[HydrusPermissions], ) -> Result<String> { let mut req_url = self.url.to_owned(); req_url.push_str("request_new_permissions?name="); - req_url.push_str(&urlencoding::encode(name)); + + req_url.push_str(name); if permissions.is_empty() { req_url.push_str("&permit_everything=true"); } else { + let json_string = &serde_json::to_string(&permissions)?; req_url.push_str("&basic_permissions="); - let json_string = musli::json::to_string(&permissions)?; - req_url.push_str(&urlencoding::encode(&json_string)); + req_url.push_str(&urlencoding::encode(json_string)); }; - let response = ureq::get(req_url).call()?.body_mut().read_to_vec()?; - - let key: AccessKey = musli::json::decode(response.as_slice())?; - - Ok(key.access_key) + Ok(self + .client + .get(req_url) + .send() + .await? + .json::<AccessKey>() + .await? + .access_key) } - pub fn get_session_key(&self) -> Result<String> { + pub async fn get_session_key(&self) -> Result<String> { let mut req_url = self.url.to_owned(); req_url.push_str("session_key"); - let mut request = ureq::get(req_url); + let mut request = self.client.get(req_url); if let Some(key) = &self.apikey { request = request.header("Hydrus-Client-API-Access-Key", key); } - let response = request.call()?.body_mut().read_to_vec()?; - - let key: SessionKey = musli::json::decode(response.as_slice())?; - - Ok(key.session_key) + Ok(request + .send() + .await? + .json::<SessionKey>() + .await? + .session_key) } - pub fn verify_access_key(&self, key: &str) -> Result<KeyInfo> { + pub async fn verify_access_key(&self, key: &str) -> Result<KeyInfo> { let mut req_url = self.url.to_owned(); req_url.push_str("verify_access_key"); - let response = ureq::get(req_url) - .header("Hydrus-Client-API-Access-Key", key) - .call()? - .body_mut() - .read_to_vec()?; - let data: KeyInfo = musli::json::decode(response.as_slice())?; - - Ok(data) + Ok(self + .client + .get(req_url) + .header("Hydrus-Client-API-Access-Key", key) + .send() + .await? + .json::<KeyInfo>() + .await?) } - fn set_get_api_key( - &self, - request: RequestBuilder<ureq::typestate::WithoutBody>, - ) -> Result<RequestBuilder<ureq::typestate::WithoutBody>> { + fn set_get_request_key(&self, url: &str) -> Result<RequestBuilder> { + let request = self.client.get(url); if let Some(key) = &self.sessionkey { Ok(request.header("Hydrus-Client-API-Access-Key", key)) } else if let Some(key) = &self.apikey { @@ -97,10 +103,8 @@ impl HydrusClient { } } - fn set_post_api_key( - &self, - request: RequestBuilder<ureq::typestate::WithBody>, - ) -> Result<RequestBuilder<ureq::typestate::WithBody>> { + fn set_post_request_key(&self, url: &str) -> Result<RequestBuilder> { + let request = self.client.post(url); if let Some(key) = &self.sessionkey { Ok(request.header("Hydrus-Client-API-Access-Key", key)) } else if let Some(key) = &self.apikey { @@ -110,52 +114,46 @@ impl HydrusClient { } } - pub fn get_service_name(&self, name: &str) -> Result<Service> { + pub async 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 = 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) + Ok(self + .set_get_request_key(&req_url)? + .send() + .await? + .json::<Service>() + .await?) } - pub fn get_service_key(&self, key: &str) -> Result<Service> { + pub async 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) + Ok(self + .set_get_request_key(&req_url)? + .send() + .await? + .json::<Service>() + .await?) } - pub fn get_services(&self) -> Result<Vec<Service>> { + pub async 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()) + Ok(self + .set_get_request_key(&req_url)? + .send() + .await? + .json::<ServiceResponse>() + .await? + .service_vec()) } - pub fn add_file_via_path( + pub async fn add_file_via_path( &self, path: &str, delete: Option<bool>, @@ -178,34 +176,35 @@ impl HydrusClient { } } - 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) + Ok(self + .set_post_request_key(&req_url)? + .header("Content-Type", "application/json") + .json(&form) + .send() + .await? + .json::<FileAddResponse>() + .await?) } - pub fn add_file_via_file(&self, file: &File) -> Result<FileAddResponse> { + pub async fn add_file_via_file(&self, file: impl AsRef<Path>) -> 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 file = tokio::fs::File::open(file).await?; + + let stream = FramedRead::new(file, BytesCodec::new()); + let body = Body::wrap_stream(stream); - let status: FileAddResponse = musli::json::decode(response.as_slice())?; - Ok(status) + Ok(self + .set_post_request_key(&req_url)? + .header("Content-Type", "application/octet-stream") + .body(body) + .send() + .await? + .json::<FileAddResponse>() + .await?) } } diff --git a/src/tests.rs b/src/tests.rs index 19d60cf..0ce410e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,50 +1,10 @@ use crate::client::HydrusClient; use crate::types::*; -#[test] -fn correct_hydruspermissions_url_encode() { - let perms: [HydrusPermissions; 3] = [ - HydrusPermissions::ImportAndEditURLs, - HydrusPermissions::ImportAndEditFiles, - HydrusPermissions::SeeLocalPaths, - ]; - let json_string = musli::json::to_string(&perms).unwrap(); - let encoded = urlencoding::encode(&json_string); - - assert_eq!(encoded, "%5B0%2C1%2C13%5D") -} - -#[test] -fn correct_hydruspermissions_decode() { - let perms: [HydrusPermissions; 3] = [ - HydrusPermissions::ImportAndEditURLs, - HydrusPermissions::ImportAndEditFiles, - HydrusPermissions::SeeLocalPaths, - ]; - let json_string = musli::json::to_string(&perms).unwrap(); - - let res: [HydrusPermissions; 3] = musli::json::from_str(&json_string).unwrap(); - assert_eq!(res, perms) -} - -#[test] -fn correct_hydrusservice_decode() { - let input: [ServiceType; 3] = [ - ServiceType::AllLocalFiles, - ServiceType::ClientAPI, - ServiceType::Trash, - ]; - - let json_string = musli::json::to_string(&input).unwrap(); - - let res: [ServiceType; 3] = musli::json::from_str(&json_string).unwrap(); - assert_eq!(res, input) -} - -#[test] -fn correct_hydrusservices_decode() { +#[tokio::test] +async fn test_service_info() { 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()) + let res = client.get_service_name("all my files").await.unwrap(); + println!("{:?}", res); } diff --git a/src/types.rs b/src/types.rs index 9c2684d..b6e9db4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,31 +1,39 @@ -use musli::{Allocator, Decode, Decoder, Encode, Encoder}; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; use std::collections::HashMap; -use strum_macros::FromRepr; use thiserror::Error; #[derive(Error, Debug)] pub enum HydrusError { #[error("failed to connect to Hydrus")] - NetworkError(ureq::Error), - #[error("failed to encode/decode data")] - DeserializeError(musli::json::Error), + NetworkError(reqwest::Error), + #[error("failed to encode/Deserialize data")] + DeserializeError(serde_json::Error), + #[error("io error")] + IOError(std::io::Error), #[error("api or session key needed")] KeyNotSupplied, } -impl From<musli::json::Error> for HydrusError { - fn from(value: musli::json::Error) -> Self { +impl From<serde_json::Error> for HydrusError { + fn from(value: serde_json::Error) -> Self { HydrusError::DeserializeError(value) } } -impl From<ureq::Error> for HydrusError { - fn from(value: ureq::Error) -> Self { +impl From<std::io::Error> for HydrusError { + fn from(value: std::io::Error) -> Self { + HydrusError::IOError(value) + } +} + +impl From<reqwest::Error> for HydrusError { + fn from(value: reqwest::Error) -> Self { HydrusError::NetworkError(value) } } -#[derive(PartialEq, Debug, Clone, FromRepr)] +#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum HydrusPermissions { ImportAndEditURLs = 0, @@ -45,47 +53,17 @@ pub enum HydrusPermissions { Null = 255, } -impl<M> Encode<M> for HydrusPermissions { - 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 HydrusPermissions -where - A: Allocator, -{ - #[inline] - fn decode<D>(decoder: D) -> Result<Self, D::Error> - where - D: Decoder<'de>, - { - Ok(HydrusPermissions::from_repr(decoder.decode()?).unwrap()) - } -} - -#[derive(Decode, Debug)] +#[derive(Deserialize, Debug)] pub struct AccessKey { pub access_key: String, } -#[derive(Decode, Debug)] +#[derive(Deserialize, Debug)] pub struct SessionKey { pub session_key: String, } -#[derive(Decode, Debug)] +#[derive(Deserialize, Debug)] pub struct KeyInfo { pub name: String, pub permits_everything: bool, @@ -93,7 +71,7 @@ pub struct KeyInfo { pub human_permissions: String, } -#[derive(PartialEq, Debug, Clone, FromRepr)] +#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum ServiceType { TagRepository = 0, @@ -118,52 +96,22 @@ pub enum ServiceType { Null = 255, } -impl<M> Encode<M> for ServiceType { - 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 ServiceType -where - A: Allocator, -{ - #[inline] - fn decode<D>(decoder: D) -> Result<Self, D::Error> - where - D: Decoder<'de>, - { - Ok(ServiceType::from_repr(decoder.decode()?).unwrap()) - } -} - -#[derive(Decode, Debug)] +#[derive(Deserialize, Debug)] pub struct Service { pub name: String, - #[musli(default)] + #[serde(default)] pub service_key: String, pub r#type: ServiceType, pub type_pretty: String, - #[musli(default)] + #[serde(default)] pub star_shape: Option<String>, - #[musli(default)] + #[serde(default)] pub min_stars: Option<u8>, - #[musli(default)] + #[serde(default)] pub max_stars: Option<u8>, } -#[derive(Decode, Debug)] +#[derive(Deserialize, Debug)] pub struct ServiceResponse { services: HashMap<String, Service>, } @@ -186,22 +134,22 @@ pub enum FileDomain { DeletedFileServiceKeys(Vec<String>), } -#[derive(Encode, Debug, Default)] +#[derive(Serialize, Debug, Default)] pub struct AddFileRequest { pub path: String, - #[musli(skip)] + #[serde(skip)] pub delete_after_successful_import: Option<bool>, - #[musli(skip)] + #[serde(skip)] pub file_service_key: Option<String>, - #[musli(skip)] + #[serde(skip)] pub file_service_keys: Option<Vec<String>>, - #[musli(skip)] + #[serde(skip)] pub deleted_file_service_key: Option<String>, - #[musli(skip)] + #[serde(skip)] pub deleted_file_service_keys: Option<Vec<String>>, } -#[derive(PartialEq, Debug, Clone, FromRepr)] +#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum FileAddStatus { SuccessfulImport = 1, @@ -211,54 +159,24 @@ pub enum FileAddStatus { 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)] +#[derive(Deserialize)] pub struct FileAddResponse { pub status: FileAddStatus, pub hash: String, pub note: String, } -#[derive(Encode, Debug, Default)] +#[derive(Serialize, Debug, Default)] pub struct DeleteFileRequest { pub path: String, - #[musli(skip)] + #[serde(skip)] pub file_service_key: Option<String>, - #[musli(skip)] + #[serde(skip)] pub file_service_keys: Option<Vec<String>>, - #[musli(skip)] + #[serde(skip)] pub deleted_file_service_key: Option<String>, - #[musli(skip)] + #[serde(skip)] pub deleted_file_service_keys: Option<Vec<String>>, - #[musli(skip)] + #[serde(skip)] pub reason: Option<String>, } |
