diff --git a/src/client.rs b/src/client.rs index 84bf161..83c1bcc 100644 --- a/src/client.rs +++ b/src/client.rs @@ -7,9 +7,14 @@ use crate::endpoints::adding_files::{ DeleteFilesRequest, UnarchiveFiles, UnarchiveFilesRequest, UndeleteFiles, UndeleteFilesRequest, }; use crate::endpoints::adding_tags::{AddTags, AddTagsRequest, CleanTags, CleanTagsResponse}; +use crate::endpoints::common::{FileIdentifier, FileRecord}; +use crate::endpoints::searching_and_fetching_files::{ + FileMetadata, FileMetadataResponse, FileSearchLocation, GetFile, SearchFiles, + SearchFilesResponse, +}; use crate::endpoints::Endpoint; use crate::error::{Error, Result}; -use crate::utils::string_list_to_json_array; +use crate::utils::{number_list_to_json_array, string_list_to_json_array}; use reqwest::Response; use serde::de::DeserializeOwned; use serde::Serialize; @@ -32,11 +37,8 @@ impl Client { }) } - /// Starts a get request to the path associated with the return type - async fn get_and_parse( - &mut self, - query: &Q, - ) -> Result { + /// Starts a get request to the path + async fn get(&mut self, query: &Q) -> Result { let response = self .inner .get(format!("{}/{}", self.base_url, E::get_path())) @@ -44,12 +46,21 @@ impl Client { .query(query) .send() .await?; - let response = Self::extract_error(response).await?; + + Self::extract_error(response).await + } + + /// Starts a get request to the path associated with the Endpoint Type + async fn get_and_parse( + &mut self, + query: &Q, + ) -> Result { + let response = self.get::(query).await?; Self::extract_content(response).await } - /// Stats a post request to the path associated with the return type + /// Stats a post request to the path associated with the Endpoint Type async fn post(&mut self, body: E::Request) -> Result { let response = self .inner @@ -179,4 +190,55 @@ impl Client { Ok(()) } + + /// Searches for files in the inbox, the archive or both + pub async fn search_files( + &mut self, + tags: Vec, + location: FileSearchLocation, + ) -> Result { + self.get_and_parse::(&[ + ("tags", string_list_to_json_array(tags)), + ("system_inbox", location.is_inbox().to_string()), + ("system_archive", location.is_archive().to_string()), + ]) + .await + } + + /// Returns the metadata for a given list of file_ids or hashes + pub async fn get_file_metadata( + &mut self, + file_ids: Vec, + hashes: Vec, + ) -> Result { + self.get_and_parse::(&[ + ("file_ids", number_list_to_json_array(file_ids)), + ("hashes", string_list_to_json_array(hashes)), + ]) + .await + } + + /// Returns the bytes of a file from hydrus + pub async fn get_file(&mut self, id: FileIdentifier) -> Result { + let response = match id { + FileIdentifier::ID(id) => { + self.get::(&[("file_id", id)]) + .await? + } + FileIdentifier::Hash(hash) => { + self.get::(&[("hash", hash)]) + .await? + } + }; + let mime_type = response + .headers() + .get("mime-type") + .cloned() + .map(|h| h.to_str().unwrap().to_string()) + .unwrap_or("image/jpeg".into()); + + let bytes = response.bytes().await?.to_vec(); + + Ok(FileRecord { bytes, mime_type }) + } } diff --git a/src/endpoints/common.rs b/src/endpoints/common.rs index 3e35771..b4e5862 100644 --- a/src/endpoints/common.rs +++ b/src/endpoints/common.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BasicServiceInfo { pub name: String, @@ -8,3 +10,34 @@ pub struct BasicServiceInfo { pub struct BasicHashList { pub hashes: Vec, } + +#[derive(Clone, Default, Deserialize)] +pub struct FileMetadataInfo { + pub file_id: u64, + pub hash: String, + pub size: u64, + pub mime: String, + pub ext: String, + pub width: u32, + pub height: u32, + pub duration: Option, + pub has_audio: bool, + pub num_frames: Option, + pub num_words: Option, + pub is_inbox: bool, + pub is_local: bool, + pub is_trashed: bool, + pub known_urls: Vec, + pub service_names_to_statuses_to_tags: HashMap>>, + pub service_names_to_statuses_to_display_tags: HashMap>>, +} + +pub enum FileIdentifier { + ID(u64), + Hash(String), +} + +pub struct FileRecord { + pub bytes: Vec, + pub mime_type: String, +} diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index e71c3c8..4adb327 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -5,6 +5,7 @@ pub mod access_management; pub mod adding_files; pub mod adding_tags; pub mod common; +pub mod searching_and_fetching_files; pub trait Endpoint { type Request: Serialize; diff --git a/src/endpoints/searching_and_fetching_files.rs b/src/endpoints/searching_and_fetching_files.rs new file mode 100644 index 0000000..aa07178 --- /dev/null +++ b/src/endpoints/searching_and_fetching_files.rs @@ -0,0 +1,77 @@ +use crate::endpoints::common::FileMetadataInfo; +use crate::endpoints::Endpoint; + +#[derive(Debug, Clone, Deserialize)] +pub struct SearchFilesResponse { + pub file_ids: Vec, +} + +pub enum FileSearchLocation { + All, + Inbox, + Archive, +} + +impl FileSearchLocation { + pub fn is_inbox(&self) -> bool { + if let &Self::Inbox = &self { + true + } else { + self.is_all() + } + } + + pub fn is_all(&self) -> bool { + if let &Self::All = &self { + true + } else { + false + } + } + + pub fn is_archive(&self) -> bool { + if let &Self::Archive = &self { + true + } else { + self.is_all() + } + } +} + +pub struct SearchFiles; + +impl Endpoint for SearchFiles { + type Request = (); + type Response = SearchFilesResponse; + + fn get_path() -> String { + String::from("get_files/search_files") + } +} + +#[derive(Clone, Default, Deserialize)] +pub struct FileMetadataResponse { + metadata: Vec, +} + +pub struct FileMetadata; + +impl Endpoint for FileMetadata { + type Request = (); + type Response = FileMetadataResponse; + + fn get_path() -> String { + String::from("get_files/file_metadata") + } +} + +pub struct GetFile; + +impl Endpoint for GetFile { + type Request = (); + type Response = (); + + fn get_path() -> String { + String::from("get_files/file") + } +} diff --git a/src/lib.rs b/src/lib.rs index 6764298..a62cf9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ extern crate serde_derive; pub mod client; pub mod endpoints; -mod error; +pub mod error; pub(crate) mod utils; pub use client::Client; diff --git a/src/utils.rs b/src/utils.rs index 729d068..da0e527 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,14 @@ pub fn string_list_to_json_array(l: Vec) -> String { format!("[\"{}\"]", l.join("\",\"")) } + +pub fn number_list_to_json_array(l: Vec) -> String { + format!( + "[{}]", + l.into_iter().fold(String::from(""), |acc, val| format!( + "{},{}", + acc, + val.to_string() + )) + ) +} diff --git a/tests/test_searching_and_fetching_files.rs b/tests/test_searching_and_fetching_files.rs new file mode 100644 index 0000000..3403579 --- /dev/null +++ b/tests/test_searching_and_fetching_files.rs @@ -0,0 +1,37 @@ +use hydrus_api::endpoints::common::FileIdentifier; +use hydrus_api::endpoints::searching_and_fetching_files::FileSearchLocation; + +mod common; + +#[tokio::test] +async fn is_searches_files() { + let mut client = common::get_client(); + client + .search_files(vec!["beach".to_string()], FileSearchLocation::Archive) + .await + .unwrap(); +} + +#[tokio::test] +async fn it_fetches_file_metadata() { + let mut client = common::get_client(); + client + .get_file_metadata( + vec![], + vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()], + ) + .await + .unwrap(); +} + +#[tokio::test] +async fn it_fetches_single_files() { + let mut client = common::get_client(); + let response = client + .get_file(FileIdentifier::Hash( + "0000000000000000000000000000000000000000000000000000000000000000".to_string(), + )) + .await; + + assert!(response.is_err()); // can't find the file +}