diff options
| author | jakka <jakka@jakka.su> | 2025-10-01 14:30:07 +0300 |
|---|---|---|
| committer | jakka <jakka@jakka.su> | 2025-10-01 14:30:07 +0300 |
| commit | 38ea76f490a94b5734d4b39ca0c0be2e6ab6f90a (patch) | |
| tree | 2fced4196723cba4e9a6dcd0d8b02af49c293c6e | |
| parent | 8680fbc0fab62e47a7736e5194badba84411ea7f (diff) | |
implemented existing functions for sync client
| -rw-r--r-- | src/async_lib/client.rs | 8 | ||||
| -rw-r--r-- | src/sync_lib/client.rs | 404 | ||||
| -rw-r--r-- | src/sync_lib/traits.rs | 2 | ||||
| -rw-r--r-- | src/types.rs | 4 |
4 files changed, 416 insertions, 2 deletions
diff --git a/src/async_lib/client.rs b/src/async_lib/client.rs index 51ed963..784f269 100644 --- a/src/async_lib/client.rs +++ b/src/async_lib/client.rs @@ -20,13 +20,17 @@ pub struct HydrusClient { } impl HydrusClient { - /// create a new hydurs client object. requires a url to hydrus API endpoint + /// create a new hydrus client object. requires a hydrus API endpoint url pub fn new(url: &str) -> HydrusClient { + let mut url = url.to_string(); + if !url.ends_with('/') { + url.push('/'); + } HydrusClient { client: reqwest::Client::new(), apikey: None, sessionkey: None, - url: url.to_string(), + url, } } /// set an api key diff --git a/src/sync_lib/client.rs b/src/sync_lib/client.rs index e69de29..a5ff726 100644 --- a/src/sync_lib/client.rs +++ b/src/sync_lib/client.rs @@ -0,0 +1,404 @@ +use crate::sync_lib::HydrusError; +use crate::sync_lib::traits::*; +use crate::types::*; +use serde::Deserialize; +use std::collections::HashMap; +use std::fs::File; +use std::path::PathBuf; +use ureq::{ + RequestBuilder, + typestate::{WithBody, WithoutBody}, +}; + +type Result<T> = std::result::Result<T, HydrusError>; + +/// hydrus client +pub struct HydrusClient { + client: ureq::Agent, + apikey: Option<String>, + sessionkey: Option<String>, + url: String, +} + +impl HydrusClient { + /// create a new hydrus client object. requires a hydrus API endpoint url + pub fn new(url: &str) -> HydrusClient { + let mut url = url.to_string(); + if !url.ends_with('/') { + url.push('/'); + } + HydrusClient { + client: ureq::Agent::new_with_defaults(), + apikey: None, + sessionkey: None, + url, + } + } + /// set an api key + pub fn set_api_key(&mut self, key: &str) { + self.apikey = Some(key.to_owned()) + } + /// set a session key + pub fn set_session_key(&mut self, key: &str) { + self.sessionkey = Some(key.to_owned()) + } + + fn set_get_request_key(&self, url: &str) -> Result<RequestBuilder<WithoutBody>> { + 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 { + Ok(request.header("Hydrus-Client-API-Access-Key", key)) + } else { + Err(HydrusError::KeyNotSupplied) + } + } + + fn set_post_request_key(&self, url: &str) -> Result<RequestBuilder<WithBody>> { + 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 { + Ok(request.header("Hydrus-Client-API-Access-Key", key)) + } else { + Err(HydrusError::KeyNotSupplied) + } + } +} + +#[derive(Deserialize, Debug)] +struct HydrusResponse<T> { + #[serde( + alias = "service", + alias = "services", + alias = "access_key", + alias = "session_key" + )] + body: T, +} + +impl AccessManagement for HydrusClient { + 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"); + + let mut request = self.client.get(req_url); + request = request.query("name", urlencoding::encode(name)); + + if permissions.is_empty() { + request = request.query("permit_everything", "true"); + } else { + let json_string = serde_json::to_string(&permissions)?; + request = request.query("basic_permissions", urlencoding::encode(&json_string)); + }; + + Ok(request + .call()? + .body_mut() + .read_json::<HydrusResponse<String>>()? + .body) + } + + fn get_session_key(&self) -> Result<String> { + let mut req_url = self.url.to_owned(); + req_url.push_str("session_key"); + + let mut request = self.client.get(req_url); + + if let Some(key) = &self.apikey { + request = request.header("Hydrus-Client-API-Access-Key", key); + } + + Ok(request + .call()? + .body_mut() + .read_json::<HydrusResponse<String>>()? + .body) + } + + fn verify_access_key(&self, key: &str) -> Result<KeyInfo> { + let mut req_url = self.url.to_owned(); + req_url.push_str("verify_access_key"); + + Ok(self + .client + .get(req_url) + .header("Hydrus-Client-API-Access-Key", key) + .call()? + .body_mut() + .read_json::<KeyInfo>()?) + } + + fn get_service_name(&self, name: &str) -> Result<Service> { + let mut req_url = self.url.to_owned(); + req_url.push_str("get_service"); + let mut request = self.set_get_request_key(&req_url)?; + request = request.query("service_name", urlencoding::encode(name)); + + Ok(request + .call()? + .body_mut() + .read_json::<HydrusResponse<Service>>()? + .body) + } + + fn get_service_key(&self, key: &str) -> Result<Service> { + let mut req_url = self.url.to_owned(); + req_url.push_str("get_service"); + let mut request = self.set_get_request_key(&req_url)?; + request = request.query("service_key", urlencoding::encode(key)); + + Ok(request + .call()? + .body_mut() + .read_json::<HydrusResponse<Service>>()? + .body) + } + + fn get_services(&self) -> Result<HashMap<String, Service>> { + let mut req_url = self.url.to_owned(); + req_url.push_str("get_services"); + + let mut services = self + .set_get_request_key(&req_url)? + .call()? + .body_mut() + .read_json::<HydrusResponse<HashMap<String, Service>>>()? + .body; + + for (key, service) in services.iter_mut() { + service.service_key = key.to_string(); + } + + Ok(services) + } +} + +impl ImportingAndDeletingFiles for HydrusClient { + fn add_file_via_path( + &self, + path: PathBuf, + delete: Option<bool>, + domains: Option<FileDomain>, + ) -> Result<AddFileResponse> { + let mut form = AddFileRequest { + path: path.to_owned(), + delete_after_successful_import: delete, + ..Default::default() + }; + + if let Some(file_domain) = domains { + match file_domain { + FileDomain::FileServiceKey(key) => form.file_service_key = Some(key.to_string()), + FileDomain::FileServiceKeys(keys) => form.file_service_keys = Some(keys.to_owned()), + FileDomain::DeletedFileServiceKey(key) => { + form.deleted_file_service_key = Some(key.to_string()) + } + FileDomain::DeletedFileServiceKeys(keys) => { + form.deleted_file_service_keys = Some(keys.to_owned()) + } + } + } + + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/add_file"); + + Ok(self + .set_post_request_key(&req_url)? + .send_json(&form)? + .body_mut() + .read_json::<AddFileResponse>()?) + } + + fn add_file_via_file(&self, file: PathBuf) -> Result<AddFileResponse> { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/add_file"); + + let file = File::open(&file)?; + + Ok(self + .set_post_request_key(&req_url)? + .header("Content-Type", "application/octet-stream") + .send(&file)? + .body_mut() + .read_json::<AddFileResponse>()?) + } + + fn delete_files( + &self, + file: HydrusFile, + domain: Option<FileDomain>, + reason: Option<String>, + ) -> Result<()> { + let mut form = FileRequest { + file, + reason, + ..Default::default() + }; + + if let Some(file_domain) = domain { + match file_domain { + FileDomain::FileServiceKey(key) => form.file_service_key = Some(key.to_string()), + FileDomain::FileServiceKeys(keys) => form.file_service_keys = Some(keys.to_owned()), + FileDomain::DeletedFileServiceKey(key) => { + form.deleted_file_service_key = Some(key.to_string()) + } + FileDomain::DeletedFileServiceKeys(keys) => { + form.deleted_file_service_keys = Some(keys.to_owned()) + } + } + } + + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/delete_files"); + + let _ = self.set_post_request_key(&req_url)?.send_json(&form)?; + + Ok(()) + } + + fn undelete_files(&self, file: HydrusFile, domain: Option<FileDomain>) -> Result<()> { + let mut form = FileRequest { + file, + ..Default::default() + }; + + if let Some(file_domain) = domain { + match file_domain { + FileDomain::FileServiceKey(key) => form.file_service_key = Some(key.to_string()), + FileDomain::FileServiceKeys(keys) => form.file_service_keys = Some(keys.to_owned()), + FileDomain::DeletedFileServiceKey(key) => { + form.deleted_file_service_key = Some(key.to_string()) + } + FileDomain::DeletedFileServiceKeys(keys) => { + form.deleted_file_service_keys = Some(keys.to_owned()) + } + } + } + + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/undelete_files"); + + let _ = self.set_post_request_key(&req_url)?.send_json(&form)?; + + Ok(()) + } + + fn clear_file_deletion_records(&self, file: HydrusFile) -> Result<()> { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/clear_file_deletion_record"); + let _ = self.set_post_request_key(&req_url)?.send_json(&file)?; + + Ok(()) + } + + fn migrate_files(&self, file: HydrusFile, domain: FileDomain) -> Result<()> { + let mut form = FileRequest { + file, + ..Default::default() + }; + + match domain { + FileDomain::FileServiceKey(key) => form.file_service_key = Some(key.to_string()), + FileDomain::FileServiceKeys(keys) => form.file_service_keys = Some(keys.to_owned()), + FileDomain::DeletedFileServiceKey(key) => { + form.deleted_file_service_key = Some(key.to_string()) + } + FileDomain::DeletedFileServiceKeys(keys) => { + form.deleted_file_service_keys = Some(keys.to_owned()) + } + } + + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/migrate_files"); + let _ = self.set_post_request_key(&req_url)?.send_json(&form)?; + + Ok(()) + } + + fn archive_files(&self, file: HydrusFile) -> Result<()> { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/archive_files"); + + let form = FileRequest { + file, + ..Default::default() + }; + + let _ = self.set_post_request_key(&req_url)?.send_json(&form)?; + + Ok(()) + } + + fn unarchive_files(&self, file: HydrusFile) -> Result<()> { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/unarchive_files"); + + let form = FileRequest { + file, + ..Default::default() + }; + + let _ = self.set_post_request_key(&req_url)?.send_json(&form)?; + + Ok(()) + } + + fn generate_hashes_for_path(&self, file: PathBuf) -> Result<HashResponse> { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/generate_hashes"); + + let form = AddFileRequest { + path: file.to_owned(), + ..Default::default() + }; + + Ok(self + .set_post_request_key(&req_url)? + .send_json(&form)? + .body_mut() + .read_json::<HashResponse>()?) + } + + fn generate_hashes_for_file(&self, file: PathBuf) -> Result<HashResponse> { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/generate_hashes"); + + let file = File::open(file)?; + + Ok(self + .set_post_request_key(&req_url)? + .header("Content-Type", "application/octet-stream") + .send(&file)? + .body_mut() + .read_json::<HashResponse>()?) + } +} + +impl ImportingAndEditingUrls for HydrusClient { + fn get_url_files( + &self, + url: &str, + doublecheck_file_system: Option<bool>, + ) -> Result<FilesUrlResponse> { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_urls/get_url_files"); + + let mut request = self.set_get_request_key(&req_url)?; + + request = request.query("url", url); + + if let Some(doublecheck) = doublecheck_file_system { + request = request.query( + "doublecheck_file_system", + if doublecheck { "true" } else { "false" }, + ); + } + + Ok(request.call()?.body_mut().read_json::<FilesUrlResponse>()?) + } +} diff --git a/src/sync_lib/traits.rs b/src/sync_lib/traits.rs index 4cc40ca..76e3875 100644 --- a/src/sync_lib/traits.rs +++ b/src/sync_lib/traits.rs @@ -59,7 +59,9 @@ pub trait ImportingAndDeletingFiles { fn generate_hashes_for_file(&self, file: PathBuf) -> Result<HashResponse>; } +/// Trait for importing and editing file urls. pub trait ImportingAndEditingUrls { + /// Ask the client about an URL's files. fn get_url_files( &self, url: &str, diff --git a/src/types.rs b/src/types.rs index e27836e..e10a48e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -186,6 +186,7 @@ impl Serialize for FileRequest { } } +/// object for per-item hash response #[derive(Debug, Deserialize)] pub struct HashResponse { pub hash: String, @@ -195,6 +196,7 @@ pub struct HashResponse { pub pixel_hash: Option<String>, } +/// url status in hydrus database #[derive(Debug, Deserialize_repr)] #[repr(u8)] pub enum UrlStatus { @@ -203,6 +205,7 @@ pub enum UrlStatus { PreviouslyDeleted, } +/// per-url response object #[derive(Debug, Deserialize)] pub struct UrlFileStatus { pub status: UrlStatus, @@ -210,6 +213,7 @@ pub struct UrlFileStatus { pub note: String, } +/// hydrus response after querying for files url status #[derive(Debug, Deserialize)] pub struct FilesUrlResponse { pub normalised_url: String, |
