summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client.rs234
-rw-r--r--src/traits.rs41
-rw-r--r--src/types.rs112
3 files changed, 330 insertions, 57 deletions
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<String> {
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::<HydrusResponse<String>>()
@@ -134,11 +132,11 @@ impl AccessManagement for HydrusClient {
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));
+ 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::<HydrusResponse<Service>>()
@@ -148,11 +146,11 @@ impl AccessManagement for HydrusClient {
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));
+ 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::<HydrusResponse<Service>>()
@@ -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::<AddFileResponse>()
.await?)
}
+
+ async 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)?
+ .header("Content-Type", "application/json")
+ .json(&form)
+ .send()
+ .await?;
+
+ Ok(())
+ }
+
+ async 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)?
+ .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<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)?
+ .header("Content-Type", "application/json")
+ .json(&form)
+ .send()
+ .await?
+ .json::<HashResponse>()
+ .await?)
+ }
+
+ async 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 = 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::<HashResponse>()
+ .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<T> = std::result::Result<T, HydrusError>;
-/// 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<String>;
- /// request new session key
+ /// Get a new session key.
async fn get_session_key(&self) -> Result<String>;
- /// verify session or api key
+ /// Check your access key is valid.
async fn verify_access_key(&self, key: &str) -> Result<KeyInfo>;
- /// 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<Service>;
- /// 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<Service>;
- /// get all service info
+ /// Ask the client about its services.
async fn get_services(&self) -> Result<HashMap<String, Service>>;
}
-/// 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<bool>,
domains: Option<FileDomain>,
) -> Result<AddFileResponse>;
- /// 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<AddFileResponse>;
+ /// Tell the client to send files to the trash.
+ async fn delete_files(
+ &self,
+ file: HydrusFile,
+ domain: Option<FileDomain>,
+ reason: Option<String>,
+ ) -> 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<FileDomain>) -> 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<HashResponse>;
+ /// Generate hashes for an arbitrary file by sending the file.
+ async fn generate_hashes_for_file(&self, file: PathBuf) -> Result<HashResponse>;
}
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<reqwest::Error> 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<u8>,
}
-/// hydrus file domains
+/// Hydrus file domains
pub enum FileDomain {
FileServiceKey(String),
FileServiceKeys(Vec<String>),
@@ -115,22 +115,22 @@ pub enum FileDomain {
DeletedFileServiceKeys(Vec<String>),
}
-/// 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<bool>,
- #[serde(skip)]
+ #[serde(skip_serializing_if = "Option::is_none")]
pub file_service_key: Option<String>,
- #[serde(skip)]
+ #[serde(skip_serializing_if = "Option::is_none")]
pub file_service_keys: Option<Vec<String>>,
- #[serde(skip)]
+ #[serde(skip_serializing_if = "Option::is_none")]
pub deleted_file_service_key: Option<String>,
- #[serde(skip)]
+ #[serde(skip_serializing_if = "Option::is_none")]
pub deleted_file_service_keys: Option<Vec<String>>,
}
-/// 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<String>),
+ #[serde(rename(serialize = "hash"))]
+ Hash(String),
+ #[serde(rename(serialize = "hashes"))]
+ Hashes(Vec<String>),
+}
+
+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<bool>,
pub file_service_key: Option<String>,
- #[serde(skip)]
pub file_service_keys: Option<Vec<String>>,
- #[serde(skip)]
pub deleted_file_service_key: Option<String>,
- #[serde(skip)]
pub deleted_file_service_keys: Option<Vec<String>>,
- #[serde(skip)]
pub reason: Option<String>,
}
+
+impl Serialize for FileRequest {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ 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<Vec<String>>,
+ #[serde(default)]
+ pub pixel_hash: Option<String>,
+}