From 6b0a6c0ca36c181d55c78a5dca46f1890c9cce38 Mon Sep 17 00:00:00 2001 From: jakka Date: Thu, 18 Sep 2025 17:12:57 +0300 Subject: cleaned up documentation. added the rest of file importing and deleting api functions --- src/client.rs | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ src/traits.rs | 41 +++++++--- src/types.rs | 112 ++++++++++++++++++++++------ 3 files changed, 330 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/client.rs b/src/client.rs index d8c7c40..c73b962 100644 --- a/src/client.rs +++ b/src/client.rs @@ -78,21 +78,19 @@ impl AccessManagement for HydrusClient { permissions: &[HydrusPermissions], ) -> Result { let mut req_url = self.url.to_owned(); - req_url.push_str("request_new_permissions?name="); + req_url.push_str("request_new_permissions"); - req_url.push_str(name); + let mut request = self.client.get(req_url); + request = request.query(&[("name", &urlencoding::encode(&name))]); if permissions.is_empty() { - req_url.push_str("&permit_everything=true"); + request = request.query(&[("permit_everything", "true")]); } else { - let json_string = &serde_json::to_string(&permissions)?; - req_url.push_str("&basic_permissions="); - req_url.push_str(&urlencoding::encode(json_string)); + let json_string = serde_json::to_string(&permissions)?; + request = request.query(&[("basic_permissions", &urlencoding::encode(&json_string))]); }; - Ok(self - .client - .get(req_url) + Ok(request .send() .await? .json::>() @@ -134,11 +132,11 @@ impl AccessManagement for HydrusClient { async 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)); + 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(self - .set_get_request_key(&req_url)? + Ok(request .send() .await? .json::>() @@ -148,11 +146,11 @@ impl AccessManagement for HydrusClient { async 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)); + 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(self - .set_get_request_key(&req_url)? + Ok(request .send() .await? .json::>() @@ -194,13 +192,15 @@ impl ImportingAndDeletingFiles for HydrusClient { ..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), + 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) + form.deleted_file_service_keys = Some(keys.to_owned()) } } } @@ -236,4 +236,192 @@ impl ImportingAndDeletingFiles for HydrusClient { .json::() .await?) } + + async fn delete_files( + &self, + file: HydrusFile, + domain: Option, + reason: Option, + ) -> 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)? + .header("Content-Type", "application/json") + .json(&form) + .send() + .await?; + + Ok(()) + } + + async fn undelete_files(&self, file: HydrusFile, domain: Option) -> 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)? + .header("Content-Type", "application/json") + .json(&form) + .send() + .await?; + + Ok(()) + } + + async 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)? + .header("Content-Type", "application/json") + .json(&file) + .send() + .await?; + + Ok(()) + } + + async 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)? + .header("Content-Type", "application/json") + .json(&form) + .send() + .await?; + + Ok(()) + } + + async 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)? + .header("Content-Type", "application/json") + .json(&form) + .send() + .await?; + + Ok(()) + } + + async 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)? + .header("Content-Type", "application/json") + .json(&form) + .send() + .await?; + + Ok(()) + } + + async fn generate_hashes_for_path(&self, file: PathBuf) -> Result { + 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)? + .header("Content-Type", "application/json") + .json(&form) + .send() + .await? + .json::() + .await?) + } + + async fn generate_hashes_for_file(&self, file: PathBuf) -> Result { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/generate_hashes"); + + let file = tokio::fs::File::open(file).await?; + + let stream = FramedRead::new(file, BytesCodec::new()); + let body = Body::wrap_stream(stream); + + Ok(self + .set_post_request_key(&req_url)? + .header("Content-Type", "application/octet-stream") + .body(body) + .send() + .await? + .json::() + .await?) + } } diff --git a/src/traits.rs b/src/traits.rs index d3390a5..6cdf094 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,37 +5,58 @@ use async_trait::async_trait; type Result = std::result::Result; -/// trait for accessing and managing keys and services +/// Trait for accessing and managing keys and services. #[async_trait] pub trait AccessManagement { - /// request new api key + /// Register a new external program with the client. This requires the 'add from api request' mini-dialog under services->review services to be open, otherwise it will 403. async fn request_new_permissions( &self, name: &str, permissions: &[HydrusPermissions], ) -> Result; - /// request new session key + /// Get a new session key. async fn get_session_key(&self) -> Result; - /// verify session or api key + /// Check your access key is valid. async fn verify_access_key(&self, key: &str) -> Result; - /// get service info by providing service name + /// Ask the client about a specific service by providing its name. async fn get_service_name(&self, name: &str) -> Result; - /// get service info by providing service key + /// Ask the client about a specific service by providing its key. async fn get_service_key(&self, key: &str) -> Result; - /// get all service info + /// Ask the client about its services. async fn get_services(&self) -> Result>; } -/// trait for importing and deleting files +/// Trait for importing and deleting files. #[async_trait] pub trait ImportingAndDeletingFiles { - /// import file into hydrus by providing a local (hydrus-local) file path + /// Tell the client to import a file by providing a local (hydrus-local) file path. async fn add_file_via_path( &self, path: PathBuf, delete: Option, domains: Option, ) -> Result; - /// import file into hydrus by sending the file + /// Tell the client to import a file by sending the file. async fn add_file_via_file(&self, file: PathBuf) -> Result; + /// Tell the client to send files to the trash. + async fn delete_files( + &self, + file: HydrusFile, + domain: Option, + reason: Option, + ) -> Result<()>; + /// Tell the client to restore files that were previously deleted to their old file service(s). + async fn undelete_files(&self, file: HydrusFile, domain: Option) -> Result<()>; + /// Tell the client to forget that it once deleted files. + async fn clear_file_deletion_records(&self, file: HydrusFile) -> Result<()>; + /// Copy files from one local file domain to another. + async fn migrate_files(&self, file: HydrusFile, domain: FileDomain) -> Result<()>; + /// Tell the client to archive inboxed files. + async fn archive_files(&self, file: HydrusFile) -> Result<()>; + /// Tell the client re-inbox archived files. + async fn unarchive_files(&self, file: HydrusFile) -> Result<()>; + /// Generate hashes for an arbitrary file by providing a local path to the file. + async fn generate_hashes_for_path(&self, file: PathBuf) -> Result; + /// Generate hashes for an arbitrary file by sending the file. + async fn generate_hashes_for_file(&self, file: PathBuf) -> Result; } diff --git a/src/types.rs b/src/types.rs index 39bb3b7..df2c387 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use thiserror::Error; -/// various errors +/// Error wrapper #[derive(Error, Debug)] pub enum HydrusError { #[error("failed to connect to Hydrus")] @@ -35,7 +35,7 @@ impl From for HydrusError { } } -/// hydrus permissions +/// Hydrus serivce permissions object #[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum HydrusPermissions { @@ -56,7 +56,7 @@ pub enum HydrusPermissions { Null = 255, } -/// hydrus key information struct +/// Hydrus key information struct #[derive(Deserialize, Debug)] pub struct KeyInfo { pub name: String, @@ -65,7 +65,7 @@ pub struct KeyInfo { pub human_permissions: String, } -/// hydrus service type struct +/// Hydrus service type object #[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum ServiceType { @@ -91,8 +91,8 @@ pub enum ServiceType { Null = 255, } -/// hydrus service struct -#[derive(Deserialize, Debug)] +/// Hydrus service struct +#[derive(Deserialize, Debug, Clone)] pub struct Service { pub name: String, #[serde(default)] @@ -107,7 +107,7 @@ pub struct Service { pub max_stars: Option, } -/// hydrus file domains +/// Hydrus file domains pub enum FileDomain { FileServiceKey(String), FileServiceKeys(Vec), @@ -115,22 +115,22 @@ pub enum FileDomain { DeletedFileServiceKeys(Vec), } -/// payload for importing a file via providing a local path +/// Payload for importing a file via providing a local path #[derive(Serialize, Debug, Default)] pub struct AddFileRequest { pub path: PathBuf, - #[serde(skip)] + #[serde(skip_serializing_if = "Option::is_none")] pub delete_after_successful_import: Option, - #[serde(skip)] + #[serde(skip_serializing_if = "Option::is_none")] pub file_service_key: Option, - #[serde(skip)] + #[serde(skip_serializing_if = "Option::is_none")] pub file_service_keys: Option>, - #[serde(skip)] + #[serde(skip_serializing_if = "Option::is_none")] pub deleted_file_service_key: Option, - #[serde(skip)] + #[serde(skip_serializing_if = "Option::is_none")] pub deleted_file_service_keys: Option>, } -/// file importing status +/// File importing status #[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] pub enum AddFileStatus { @@ -140,25 +140,89 @@ pub enum AddFileStatus { FailedToImport, FileVetoed = 7, } -/// file importing api response +/// File importing api response #[derive(Deserialize)] pub struct AddFileResponse { pub status: AddFileStatus, pub hash: String, pub note: String, } -/// payload for deleting a file -#[derive(Serialize, Debug, Default)] -pub struct DeleteFileRequest { - pub path: String, - #[serde(skip)] + +/// Hydrus file object +#[derive(Debug, Clone, Serialize)] +pub enum HydrusFile { + #[serde(rename(serialize = "file_id"))] + FileId(String), + #[serde(rename(serialize = "file_ids"))] + FileIds(Vec), + #[serde(rename(serialize = "hash"))] + Hash(String), + #[serde(rename(serialize = "hashes"))] + Hashes(Vec), +} + +impl Default for HydrusFile { + fn default() -> Self { + Self::FileId(String::from("")) + } +} + +/// Payload for various file-related requests +#[derive(Debug, Default)] +pub struct FileRequest { + pub file: HydrusFile, + pub delete_after_successful_import: Option, pub file_service_key: Option, - #[serde(skip)] pub file_service_keys: Option>, - #[serde(skip)] pub deleted_file_service_key: Option, - #[serde(skip)] pub deleted_file_service_keys: Option>, - #[serde(skip)] pub reason: Option, } + +impl Serialize for FileRequest { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeMap; + let mut map = serializer.serialize_map(Some(1))?; + + match &self.file { + HydrusFile::FileId(id) => map.serialize_entry("file_id", &id)?, + HydrusFile::FileIds(ids) => map.serialize_entry("file_ids", &ids)?, + HydrusFile::Hash(hash) => map.serialize_entry("hash", &hash)?, + HydrusFile::Hashes(hashes) => map.serialize_entry("hashes", &hashes)?, + } + + if let Some(val) = &self.reason { + map.serialize_entry("reason", &val)?; + } + + if let Some(val) = &self.file_service_key { + map.serialize_entry("file_service_key", &val)?; + } + + if let Some(val) = &self.file_service_keys { + map.serialize_entry("file_service_keys", &val)?; + } + + if let Some(val) = &self.deleted_file_service_key { + map.serialize_entry("deleted_file_service_key", &val)?; + } + + if let Some(val) = &self.deleted_file_service_keys { + map.serialize_entry("deleted_file_service_keys", &val)?; + } + + map.end() + } +} + +#[derive(Debug, Deserialize)] +pub struct HashResponse { + pub hash: String, + #[serde(default)] + pub perceptual_hashes: Option>, + #[serde(default)] + pub pixel_hash: Option, +} -- cgit v1.3.1