From 228f24cb3020b719540fc9665e4aff21528fc69e Mon Sep 17 00:00:00 2001 From: jakka Date: Wed, 1 Oct 2025 12:57:32 +0300 Subject: broke down modules and renamed them --- Cargo.toml | 1 + src/async_lib.rs | 33 ++++ src/async_lib/client.rs | 449 ++++++++++++++++++++++++++++++++++++++++++++++++ src/async_lib/traits.rs | 71 ++++++++ src/async_lib/types.rs | 249 +++++++++++++++++++++++++++ src/client_async.rs | 449 ------------------------------------------------ src/lib.rs | 17 +- src/sync_lib.rs | 3 + src/sync_lib/client.rs | 0 src/sync_lib/traits.rs | 67 ++++++++ src/sync_lib/types.rs | 249 +++++++++++++++++++++++++++ src/tests.rs | 27 --- src/traits_async.rs | 71 -------- src/types.rs | 249 --------------------------- 14 files changed, 1128 insertions(+), 807 deletions(-) create mode 100644 src/async_lib.rs create mode 100644 src/async_lib/client.rs create mode 100644 src/async_lib/traits.rs create mode 100644 src/async_lib/types.rs delete mode 100644 src/client_async.rs create mode 100644 src/sync_lib.rs create mode 100644 src/sync_lib/client.rs create mode 100644 src/sync_lib/traits.rs create mode 100644 src/sync_lib/types.rs delete mode 100644 src/tests.rs delete mode 100644 src/traits_async.rs delete mode 100644 src/types.rs diff --git a/Cargo.toml b/Cargo.toml index 02c7815..7abd94e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,5 +19,6 @@ ureq = { version = "3.1.2", features = ["json"], optional = true } urlencoding = "2.1.3" [features] +deafult = ["sync"] async = ["dep:async-trait", "dep:reqwest", "dep:tokio", "dep:tokio-util"] sync = ["dep:ureq"] diff --git a/src/async_lib.rs b/src/async_lib.rs new file mode 100644 index 0000000..6bbdd22 --- /dev/null +++ b/src/async_lib.rs @@ -0,0 +1,33 @@ +pub mod client; +pub mod traits; +pub mod types; + +#[cfg(test)] +mod tests { + use crate::async_lib::{client::*, traits::*}; + + #[tokio::test] + async fn test_service_name_info() { + let mut client: HydrusClient = HydrusClient::new("http://127.0.0.1:51251/"); + client.set_api_key("7ab7accf6cf12b2c6c30436cd8fe16361aee33679dbd90da279b5c22b33d622a"); + let _ = client.get_service_name("all my files").await.unwrap(); + } + + #[tokio::test] + async fn test_service_key_info() { + let mut client: HydrusClient = HydrusClient::new("http://127.0.0.1:51251/"); + client.set_api_key("7ab7accf6cf12b2c6c30436cd8fe16361aee33679dbd90da279b5c22b33d622a"); + let _ = client + .get_service_key("616c6c206c6f63616c206d65646961") + .await + .unwrap(); + } + + #[tokio::test] + async fn test_get_services() { + let mut client: HydrusClient = HydrusClient::new("http://127.0.0.1:51251/"); + client.set_api_key("7ab7accf6cf12b2c6c30436cd8fe16361aee33679dbd90da279b5c22b33d622a"); + let res = client.get_services().await.unwrap(); + assert!(!res.is_empty()) + } +} diff --git a/src/async_lib/client.rs b/src/async_lib/client.rs new file mode 100644 index 0000000..58e3b83 --- /dev/null +++ b/src/async_lib/client.rs @@ -0,0 +1,449 @@ +use std::{collections::HashMap, path::PathBuf}; + +use async_trait::async_trait; +use reqwest::{Body, RequestBuilder}; +use serde::Deserialize; +use tokio_util::codec::{BytesCodec, FramedRead}; + +use crate::async_lib::{traits::*, types::*}; + +type Result = std::result::Result; + +/// hydrus client +pub struct HydrusClient { + client: reqwest::Client, + apikey: Option, + sessionkey: Option, + url: String, +} + +impl HydrusClient { + /// create a new hydurs client object. requires a url to hydrus API endpoint + pub fn new(url: &str) -> HydrusClient { + HydrusClient { + client: reqwest::Client::new(), + apikey: None, + sessionkey: None, + url: url.to_string(), + } + } + /// 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 { + 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 { + 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 { + #[serde( + alias = "service", + alias = "services", + alias = "access_key", + alias = "session_key" + )] + body: T, +} + +#[async_trait] +impl AccessManagement for HydrusClient { + async fn request_new_permissions( + &self, + name: &str, + permissions: &[HydrusPermissions], + ) -> Result { + 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 + .send() + .await? + .json::>() + .await? + .body) + } + + async fn get_session_key(&self) -> Result { + 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 + .send() + .await? + .json::>() + .await? + .body) + } + + async fn verify_access_key(&self, key: &str) -> Result { + 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) + .send() + .await? + .json::() + .await?) + } + + async fn get_service_name(&self, name: &str) -> Result { + 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 + .send() + .await? + .json::>() + .await? + .body) + } + + async fn get_service_key(&self, key: &str) -> Result { + 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 + .send() + .await? + .json::>() + .await? + .body) + } + + async fn get_services(&self) -> Result> { + let mut req_url = self.url.to_owned(); + req_url.push_str("get_services"); + + let mut services = self + .set_get_request_key(&req_url)? + .send() + .await? + .json::>>() + .await? + .body; + + for (key, service) in services.iter_mut() { + service.service_key = key.to_string(); + } + + Ok(services) + } +} + +#[async_trait] +impl ImportingAndDeletingFiles for HydrusClient { + async fn add_file_via_path( + &self, + path: PathBuf, + delete: Option, + domains: Option, + ) -> Result { + 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)? + .header("Content-Type", "application/json") + .json(&form) + .send() + .await? + .json::() + .await?) + } + + async fn add_file_via_file(&self, file: PathBuf) -> Result { + let mut req_url = self.url.to_owned(); + req_url.push_str("add_files/add_file"); + + 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?) + } + + 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?) + } +} + +#[async_trait] +impl ImportingAndEditingUrls for HydrusClient { + async fn get_url_files( + &self, + url: &str, + doublecheck_file_system: Option, + ) -> Result { + 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", doublecheck)]); + } + + Ok(request.send().await?.json::().await?) + } +} diff --git a/src/async_lib/traits.rs b/src/async_lib/traits.rs new file mode 100644 index 0000000..8177968 --- /dev/null +++ b/src/async_lib/traits.rs @@ -0,0 +1,71 @@ +use std::{collections::HashMap, path::PathBuf}; + +use crate::async_lib::types::*; +use async_trait::async_trait; + +type Result = std::result::Result; + +/// Trait for accessing and managing keys and services. +#[async_trait] +pub trait AccessManagement { + /// 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; + /// Get a new session key. + async fn get_session_key(&self) -> Result; + /// Check your access key is valid. + async fn verify_access_key(&self, key: &str) -> Result; + /// Ask the client about a specific service by providing its name. + async fn get_service_name(&self, name: &str) -> Result; + /// Ask the client about a specific service by providing its key. + async fn get_service_key(&self, key: &str) -> Result; + /// Ask the client about its services. + async fn get_services(&self) -> Result>; +} + +/// Trait for importing and deleting files. +#[async_trait] +pub trait ImportingAndDeletingFiles { + /// 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; + /// 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; +} + +#[async_trait] +pub trait ImportingAndEditingUrls { + async fn get_url_files( + &self, + url: &str, + doublecheck_file_system: Option, + ) -> Result; +} diff --git a/src/async_lib/types.rs b/src/async_lib/types.rs new file mode 100644 index 0000000..7762065 --- /dev/null +++ b/src/async_lib/types.rs @@ -0,0 +1,249 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use thiserror::Error; + +/// Error wrapper +#[derive(Error, Debug)] +pub enum HydrusError { + #[error("failed to connect to Hydrus")] + 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 for HydrusError { + fn from(value: serde_json::Error) -> Self { + HydrusError::DeserializeError(value) + } +} + +impl From for HydrusError { + fn from(value: std::io::Error) -> Self { + HydrusError::IOError(value) + } +} + +impl From for HydrusError { + fn from(value: reqwest::Error) -> Self { + HydrusError::NetworkError(value) + } +} + +/// Hydrus serivce permissions object +#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum HydrusPermissions { + ImportAndEditURLs = 0, + ImportAndEditFiles, + EditFileTags, + SearchAndFetchFiles, + ManagePages, + ManageCookiesAndHeaders, + ManageDatabase, + EditFileNotes, + EditFileRelationships, + EditFileRatings, + ManagePopups, + EditFileTimes, + CommitPending, + SeeLocalPaths, + Null = 255, +} + +/// Hydrus key information struct +#[derive(Deserialize, Debug)] +pub struct KeyInfo { + pub name: String, + pub permits_everything: bool, + pub basic_permissions: Vec, + pub human_permissions: String, +} + +/// Hydrus service type object +#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum ServiceType { + TagRepository = 0, + FileRepository, + LocalFileDomain, + LocalTagDomain = 5, + NumericalRating, + BoolRating, + AllKnownTags = 10, + AllKnownFiles, + LocalBooru, + IPFS, + Trash, + AllLocalFiles, + FileNotes = 17, + ClientAPI, + DeletedFromAnywhere, + LocalUpdates, + AllMyFiles, + IncDecRating, + ServerAdmin = 99, + Null = 255, +} + +/// Hydrus service struct +#[derive(Deserialize, Debug, Clone)] +pub struct Service { + pub name: String, + #[serde(default)] + pub service_key: String, + pub r#type: ServiceType, + pub type_pretty: String, + #[serde(default)] + pub star_shape: Option, + #[serde(default)] + pub min_stars: Option, + #[serde(default)] + pub max_stars: Option, +} + +/// Hydrus file domains +pub enum FileDomain { + FileServiceKey(String), + FileServiceKeys(Vec), + DeletedFileServiceKey(String), + DeletedFileServiceKeys(Vec), +} + +/// Payload for importing a file via providing a local path +#[derive(Serialize, Debug, Default)] +pub struct AddFileRequest { + pub path: PathBuf, + #[serde(skip_serializing_if = "Option::is_none")] + pub delete_after_successful_import: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub file_service_key: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub file_service_keys: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub deleted_file_service_key: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub deleted_file_service_keys: Option>, +} +/// File importing status +#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum AddFileStatus { + SuccessfulImport = 1, + AlreadyInDatabase, + PreviouslyDeleted, + FailedToImport, + FileVetoed = 7, +} +/// File importing api response +#[derive(Deserialize)] +pub struct AddFileResponse { + pub status: AddFileStatus, + pub hash: String, + pub note: String, +} + +/// 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, + pub file_service_keys: Option>, + pub deleted_file_service_key: Option, + pub deleted_file_service_keys: Option>, + 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, +} + +#[derive(Debug, Deserialize_repr)] +#[repr(u8)] +pub enum UrlStatus { + NotInDatabase = 0, + AlreadyInDatabase = 2, + PreviouslyDeleted, +} + +#[derive(Debug, Deserialize)] +pub struct UrlFileStatus { + pub status: UrlStatus, + pub hash: String, + pub note: String, +} + +#[derive(Debug, Deserialize)] +pub struct FilesUrlResponse { + pub normalised_url: String, + pub url_file_statuses: Vec, +} diff --git a/src/client_async.rs b/src/client_async.rs deleted file mode 100644 index c189a06..0000000 --- a/src/client_async.rs +++ /dev/null @@ -1,449 +0,0 @@ -use std::{collections::HashMap, path::PathBuf}; - -use async_trait::async_trait; -use reqwest::{Body, RequestBuilder}; -use serde::Deserialize; -use tokio_util::codec::{BytesCodec, FramedRead}; - -use crate::{traits_async::*, types::*}; - -type Result = std::result::Result; - -/// hydrus client -pub struct HydrusClient { - client: reqwest::Client, - apikey: Option, - sessionkey: Option, - url: String, -} - -impl HydrusClient { - /// create a new hydurs client object. requires a url to hydrus API endpoint - pub fn new(url: &str) -> HydrusClient { - HydrusClient { - client: reqwest::Client::new(), - apikey: None, - sessionkey: None, - url: url.to_string(), - } - } - /// 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 { - 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 { - 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 { - #[serde( - alias = "service", - alias = "services", - alias = "access_key", - alias = "session_key" - )] - body: T, -} - -#[async_trait] -impl AccessManagement for HydrusClient { - async fn request_new_permissions( - &self, - name: &str, - permissions: &[HydrusPermissions], - ) -> Result { - 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 - .send() - .await? - .json::>() - .await? - .body) - } - - async fn get_session_key(&self) -> Result { - 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 - .send() - .await? - .json::>() - .await? - .body) - } - - async fn verify_access_key(&self, key: &str) -> Result { - 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) - .send() - .await? - .json::() - .await?) - } - - async fn get_service_name(&self, name: &str) -> Result { - 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 - .send() - .await? - .json::>() - .await? - .body) - } - - async fn get_service_key(&self, key: &str) -> Result { - 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 - .send() - .await? - .json::>() - .await? - .body) - } - - async fn get_services(&self) -> Result> { - let mut req_url = self.url.to_owned(); - req_url.push_str("get_services"); - - let mut services = self - .set_get_request_key(&req_url)? - .send() - .await? - .json::>>() - .await? - .body; - - for (key, service) in services.iter_mut() { - service.service_key = key.to_string(); - } - - Ok(services) - } -} - -#[async_trait] -impl ImportingAndDeletingFiles for HydrusClient { - async fn add_file_via_path( - &self, - path: PathBuf, - delete: Option, - domains: Option, - ) -> Result { - 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)? - .header("Content-Type", "application/json") - .json(&form) - .send() - .await? - .json::() - .await?) - } - - async fn add_file_via_file(&self, file: PathBuf) -> Result { - let mut req_url = self.url.to_owned(); - req_url.push_str("add_files/add_file"); - - 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?) - } - - 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?) - } -} - -#[async_trait] -impl ImportingAndEditingUrls for HydrusClient { - async fn get_url_files( - &self, - url: &str, - doublecheck_file_system: Option, - ) -> Result { - 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", doublecheck)]); - } - - Ok(request.send().await?.json::().await?) - } -} diff --git a/src/lib.rs b/src/lib.rs index c2a0350..d059b6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,8 @@ -//! crate with hydrus client and traits necessary for accesing hydrus API +//! crate with hydrus client and traits necessary for fully using hydrus API -/// this crate's hydrus client async implementation +/// async traits, types and client implementation #[cfg(feature = "async")] -pub mod client_async; -/// traits for accessing hydrus API -#[cfg(feature = "async")] -pub mod traits_async; -/// various objects for de/serializing requests -pub mod types; - -#[cfg(test)] -mod tests; +pub mod async_lib; +/// sync traits, types and client implementation +#[cfg(feature = "sync")] +pub mod sync_lib; diff --git a/src/sync_lib.rs b/src/sync_lib.rs new file mode 100644 index 0000000..fd28c4c --- /dev/null +++ b/src/sync_lib.rs @@ -0,0 +1,3 @@ +pub mod client; +pub mod traits; +pub mod types; diff --git a/src/sync_lib/client.rs b/src/sync_lib/client.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/sync_lib/traits.rs b/src/sync_lib/traits.rs new file mode 100644 index 0000000..1f8e1dd --- /dev/null +++ b/src/sync_lib/traits.rs @@ -0,0 +1,67 @@ +use std::{collections::HashMap, path::PathBuf}; + +use crate::sync_lib::types::*; + +type Result = std::result::Result; + +/// Trait for accessing and managing keys and services. +pub trait AccessManagement { + /// 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. + fn request_new_permissions( + &self, + name: &str, + permissions: &[HydrusPermissions], + ) -> Result; + /// Get a new session key. + fn get_session_key(&self) -> Result; + /// Check your access key is valid. + fn verify_access_key(&self, key: &str) -> Result; + /// Ask the client about a specific service by providing its name. + fn get_service_name(&self, name: &str) -> Result; + /// Ask the client about a specific service by providing its key. + fn get_service_key(&self, key: &str) -> Result; + /// Ask the client about its services. + fn get_services(&self) -> Result>; +} + +/// Trait for importing and deleting files. +pub trait ImportingAndDeletingFiles { + /// Tell the client to import a file by providing a local (hydrus-local) file path. + fn add_file_via_path( + &self, + path: PathBuf, + delete: Option, + domains: Option, + ) -> Result; + /// Tell the client to import a file by sending the file. + fn add_file_via_file(&self, file: PathBuf) -> Result; + /// Tell the client to send files to the trash. + 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). + fn undelete_files(&self, file: HydrusFile, domain: Option) -> Result<()>; + /// Tell the client to forget that it once deleted files. + fn clear_file_deletion_records(&self, file: HydrusFile) -> Result<()>; + /// Copy files from one local file domain to another. + fn migrate_files(&self, file: HydrusFile, domain: FileDomain) -> Result<()>; + /// Tell the client to archive inboxed files. + fn archive_files(&self, file: HydrusFile) -> Result<()>; + /// Tell the client re-inbox archived files. + fn unarchive_files(&self, file: HydrusFile) -> Result<()>; + /// Generate hashes for an arbitrary file by providing a local path to the file. + fn generate_hashes_for_path(&self, file: PathBuf) -> Result; + /// Generate hashes for an arbitrary file by sending the file. + fn generate_hashes_for_file(&self, file: PathBuf) -> Result; +} + +pub trait ImportingAndEditingUrls { + fn get_url_files( + &self, + url: &str, + doublecheck_file_system: Option, + ) -> Result; +} diff --git a/src/sync_lib/types.rs b/src/sync_lib/types.rs new file mode 100644 index 0000000..7762065 --- /dev/null +++ b/src/sync_lib/types.rs @@ -0,0 +1,249 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use thiserror::Error; + +/// Error wrapper +#[derive(Error, Debug)] +pub enum HydrusError { + #[error("failed to connect to Hydrus")] + 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 for HydrusError { + fn from(value: serde_json::Error) -> Self { + HydrusError::DeserializeError(value) + } +} + +impl From for HydrusError { + fn from(value: std::io::Error) -> Self { + HydrusError::IOError(value) + } +} + +impl From for HydrusError { + fn from(value: reqwest::Error) -> Self { + HydrusError::NetworkError(value) + } +} + +/// Hydrus serivce permissions object +#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum HydrusPermissions { + ImportAndEditURLs = 0, + ImportAndEditFiles, + EditFileTags, + SearchAndFetchFiles, + ManagePages, + ManageCookiesAndHeaders, + ManageDatabase, + EditFileNotes, + EditFileRelationships, + EditFileRatings, + ManagePopups, + EditFileTimes, + CommitPending, + SeeLocalPaths, + Null = 255, +} + +/// Hydrus key information struct +#[derive(Deserialize, Debug)] +pub struct KeyInfo { + pub name: String, + pub permits_everything: bool, + pub basic_permissions: Vec, + pub human_permissions: String, +} + +/// Hydrus service type object +#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum ServiceType { + TagRepository = 0, + FileRepository, + LocalFileDomain, + LocalTagDomain = 5, + NumericalRating, + BoolRating, + AllKnownTags = 10, + AllKnownFiles, + LocalBooru, + IPFS, + Trash, + AllLocalFiles, + FileNotes = 17, + ClientAPI, + DeletedFromAnywhere, + LocalUpdates, + AllMyFiles, + IncDecRating, + ServerAdmin = 99, + Null = 255, +} + +/// Hydrus service struct +#[derive(Deserialize, Debug, Clone)] +pub struct Service { + pub name: String, + #[serde(default)] + pub service_key: String, + pub r#type: ServiceType, + pub type_pretty: String, + #[serde(default)] + pub star_shape: Option, + #[serde(default)] + pub min_stars: Option, + #[serde(default)] + pub max_stars: Option, +} + +/// Hydrus file domains +pub enum FileDomain { + FileServiceKey(String), + FileServiceKeys(Vec), + DeletedFileServiceKey(String), + DeletedFileServiceKeys(Vec), +} + +/// Payload for importing a file via providing a local path +#[derive(Serialize, Debug, Default)] +pub struct AddFileRequest { + pub path: PathBuf, + #[serde(skip_serializing_if = "Option::is_none")] + pub delete_after_successful_import: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub file_service_key: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub file_service_keys: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub deleted_file_service_key: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub deleted_file_service_keys: Option>, +} +/// File importing status +#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum AddFileStatus { + SuccessfulImport = 1, + AlreadyInDatabase, + PreviouslyDeleted, + FailedToImport, + FileVetoed = 7, +} +/// File importing api response +#[derive(Deserialize)] +pub struct AddFileResponse { + pub status: AddFileStatus, + pub hash: String, + pub note: String, +} + +/// 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, + pub file_service_keys: Option>, + pub deleted_file_service_key: Option, + pub deleted_file_service_keys: Option>, + 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, +} + +#[derive(Debug, Deserialize_repr)] +#[repr(u8)] +pub enum UrlStatus { + NotInDatabase = 0, + AlreadyInDatabase = 2, + PreviouslyDeleted, +} + +#[derive(Debug, Deserialize)] +pub struct UrlFileStatus { + pub status: UrlStatus, + pub hash: String, + pub note: String, +} + +#[derive(Debug, Deserialize)] +pub struct FilesUrlResponse { + pub normalised_url: String, + pub url_file_statuses: Vec, +} diff --git a/src/tests.rs b/src/tests.rs deleted file mode 100644 index 8cb725e..0000000 --- a/src/tests.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::client_async::HydrusClient; -use crate::{traits_async::*, types::*}; - -#[tokio::test] -async fn test_service_name_info() { - let mut client: HydrusClient = HydrusClient::new("http://127.0.0.1:51251/"); - client.set_api_key("7ab7accf6cf12b2c6c30436cd8fe16361aee33679dbd90da279b5c22b33d622a"); - let _ = client.get_service_name("all my files").await.unwrap(); -} - -#[tokio::test] -async fn test_service_key_info() { - let mut client: HydrusClient = HydrusClient::new("http://127.0.0.1:51251/"); - client.set_api_key("7ab7accf6cf12b2c6c30436cd8fe16361aee33679dbd90da279b5c22b33d622a"); - let _ = client - .get_service_key("616c6c206c6f63616c206d65646961") - .await - .unwrap(); -} - -#[tokio::test] -async fn test_get_services() { - let mut client: HydrusClient = HydrusClient::new("http://127.0.0.1:51251/"); - client.set_api_key("7ab7accf6cf12b2c6c30436cd8fe16361aee33679dbd90da279b5c22b33d622a"); - let res = client.get_services().await.unwrap(); - assert!(!res.is_empty()) -} diff --git a/src/traits_async.rs b/src/traits_async.rs deleted file mode 100644 index 9c4de18..0000000 --- a/src/traits_async.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::{collections::HashMap, path::PathBuf}; - -use crate::types::*; -use async_trait::async_trait; - -type Result = std::result::Result; - -/// Trait for accessing and managing keys and services. -#[async_trait] -pub trait AccessManagement { - /// 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; - /// Get a new session key. - async fn get_session_key(&self) -> Result; - /// Check your access key is valid. - async fn verify_access_key(&self, key: &str) -> Result; - /// Ask the client about a specific service by providing its name. - async fn get_service_name(&self, name: &str) -> Result; - /// Ask the client about a specific service by providing its key. - async fn get_service_key(&self, key: &str) -> Result; - /// Ask the client about its services. - async fn get_services(&self) -> Result>; -} - -/// Trait for importing and deleting files. -#[async_trait] -pub trait ImportingAndDeletingFiles { - /// 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; - /// 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; -} - -#[async_trait] -pub trait ImportingAndEditingUrls { - async fn get_url_files( - &self, - url: &str, - doublecheck_file_system: Option, - ) -> Result; -} diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 7762065..0000000 --- a/src/types.rs +++ /dev/null @@ -1,249 +0,0 @@ -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; -use serde_repr::{Deserialize_repr, Serialize_repr}; -use thiserror::Error; - -/// Error wrapper -#[derive(Error, Debug)] -pub enum HydrusError { - #[error("failed to connect to Hydrus")] - 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 for HydrusError { - fn from(value: serde_json::Error) -> Self { - HydrusError::DeserializeError(value) - } -} - -impl From for HydrusError { - fn from(value: std::io::Error) -> Self { - HydrusError::IOError(value) - } -} - -impl From for HydrusError { - fn from(value: reqwest::Error) -> Self { - HydrusError::NetworkError(value) - } -} - -/// Hydrus serivce permissions object -#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum HydrusPermissions { - ImportAndEditURLs = 0, - ImportAndEditFiles, - EditFileTags, - SearchAndFetchFiles, - ManagePages, - ManageCookiesAndHeaders, - ManageDatabase, - EditFileNotes, - EditFileRelationships, - EditFileRatings, - ManagePopups, - EditFileTimes, - CommitPending, - SeeLocalPaths, - Null = 255, -} - -/// Hydrus key information struct -#[derive(Deserialize, Debug)] -pub struct KeyInfo { - pub name: String, - pub permits_everything: bool, - pub basic_permissions: Vec, - pub human_permissions: String, -} - -/// Hydrus service type object -#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum ServiceType { - TagRepository = 0, - FileRepository, - LocalFileDomain, - LocalTagDomain = 5, - NumericalRating, - BoolRating, - AllKnownTags = 10, - AllKnownFiles, - LocalBooru, - IPFS, - Trash, - AllLocalFiles, - FileNotes = 17, - ClientAPI, - DeletedFromAnywhere, - LocalUpdates, - AllMyFiles, - IncDecRating, - ServerAdmin = 99, - Null = 255, -} - -/// Hydrus service struct -#[derive(Deserialize, Debug, Clone)] -pub struct Service { - pub name: String, - #[serde(default)] - pub service_key: String, - pub r#type: ServiceType, - pub type_pretty: String, - #[serde(default)] - pub star_shape: Option, - #[serde(default)] - pub min_stars: Option, - #[serde(default)] - pub max_stars: Option, -} - -/// Hydrus file domains -pub enum FileDomain { - FileServiceKey(String), - FileServiceKeys(Vec), - DeletedFileServiceKey(String), - DeletedFileServiceKeys(Vec), -} - -/// Payload for importing a file via providing a local path -#[derive(Serialize, Debug, Default)] -pub struct AddFileRequest { - pub path: PathBuf, - #[serde(skip_serializing_if = "Option::is_none")] - pub delete_after_successful_import: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub file_service_key: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub file_service_keys: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub deleted_file_service_key: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub deleted_file_service_keys: Option>, -} -/// File importing status -#[derive(PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum AddFileStatus { - SuccessfulImport = 1, - AlreadyInDatabase, - PreviouslyDeleted, - FailedToImport, - FileVetoed = 7, -} -/// File importing api response -#[derive(Deserialize)] -pub struct AddFileResponse { - pub status: AddFileStatus, - pub hash: String, - pub note: String, -} - -/// 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, - pub file_service_keys: Option>, - pub deleted_file_service_key: Option, - pub deleted_file_service_keys: Option>, - 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, -} - -#[derive(Debug, Deserialize_repr)] -#[repr(u8)] -pub enum UrlStatus { - NotInDatabase = 0, - AlreadyInDatabase = 2, - PreviouslyDeleted, -} - -#[derive(Debug, Deserialize)] -pub struct UrlFileStatus { - pub status: UrlStatus, - pub hash: String, - pub note: String, -} - -#[derive(Debug, Deserialize)] -pub struct FilesUrlResponse { - pub normalised_url: String, - pub url_file_statuses: Vec, -} -- cgit v1.3.1