diff --git a/Cargo.toml b/Cargo.toml index 326b368..bcad5e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydrus-api" -version = "0.8.0" +version = "0.9.0" authors = ["trivernis "] edition = "2018" license = "Apache-2.0" diff --git a/src/api_core/client.rs b/src/api_core/client.rs index 9b3d789..8c08187 100644 --- a/src/api_core/client.rs +++ b/src/api_core/client.rs @@ -1,6 +1,5 @@ use crate::api_core::common::{ - FileIdentifier, FileMetadataInfo, FileRecord, FileSelection, FileServiceSelection, - OptionalStringNumber, + FileIdentifier, FileRecord, FileSelection, FileServiceSelection, OptionalStringNumber, }; use crate::api_core::endpoints::access_management::{ ApiVersion, ApiVersionResponse, GetServices, GetServicesResponse, SessionKey, @@ -30,8 +29,8 @@ use crate::api_core::endpoints::managing_pages::{ GetPages, GetPagesResponse, }; use crate::api_core::endpoints::searching_and_fetching_files::{ - FileMetadata, FileMetadataResponse, FileSearchOptions, GetFile, SearchFileHashes, - SearchFileHashesResponse, SearchFiles, SearchFilesResponse, SearchQueryEntry, + FileMetadata, FileMetadataResponse, FileMetadataType, FileSearchOptions, GetFile, + SearchFileHashes, SearchFileHashesResponse, SearchFiles, SearchFilesResponse, SearchQueryEntry, }; use crate::api_core::endpoints::Endpoint; use crate::error::{Error, Result}; @@ -224,29 +223,40 @@ impl Client { /// Returns the metadata for a given list of file_ids or hashes #[tracing::instrument(skip(self), level = "debug")] - pub async fn get_file_metadata( + pub async fn get_file_metadata( &self, file_ids: Vec, hashes: Vec, - ) -> Result { - let query = if file_ids.len() > 0 { + ) -> Result> { + let id_query = if file_ids.len() > 0 { ("file_ids", Self::serialize_query_object(file_ids)?) } else { ("hashes", Self::serialize_query_object(hashes)?) }; - self.get_and_parse::(&[query]) + let query = [ + id_query, + ( + "only_return_identifiers", + Self::serialize_query_object(M::only_identifiers())?, + ), + ( + "only_return_basic_information", + Self::serialize_query_object(M::only_basic_information())?, + ), + ]; + self.get_and_parse::, [(&str, String)]>(&query) .await } /// Returns the metadata for a single file identifier #[tracing::instrument(skip(self), level = "debug")] - pub async fn get_file_metadata_by_identifier( + pub async fn get_file_metadata_by_identifier( &self, id: FileIdentifier, - ) -> Result { + ) -> Result { let mut response = match id.clone() { - FileIdentifier::ID(id) => self.get_file_metadata(vec![id], vec![]).await?, - FileIdentifier::Hash(hash) => self.get_file_metadata(vec![], vec![hash]).await?, + FileIdentifier::ID(id) => self.get_file_metadata::(vec![id], vec![]).await?, + FileIdentifier::Hash(hash) => self.get_file_metadata::(vec![], vec![hash]).await?, }; response diff --git a/src/api_core/common.rs b/src/api_core/common.rs index 4ce5b9f..4a227d5 100644 --- a/src/api_core/common.rs +++ b/src/api_core/common.rs @@ -45,33 +45,6 @@ pub struct BasicHashList { pub hashes: Vec, } -#[derive(Clone, Debug, Default, Deserialize)] -pub struct FileMetadataInfo { - pub file_id: u64, - pub hash: String, - pub size: Option, - pub mime: String, - pub ext: String, - pub width: Option, - pub height: Option, - pub duration: Option, - pub time_modified: Option, - pub file_services: FileMetadataServices, - pub has_audio: Option, - pub num_frames: Option, - pub num_words: Option, - pub is_inbox: bool, - pub is_local: bool, - pub is_trashed: bool, - pub known_urls: Vec, - #[deprecated] - pub service_names_to_statuses_to_tags: HashMap>>, - pub service_keys_to_statuses_to_tags: HashMap>>, - #[deprecated] - pub service_names_to_statuses_to_display_tags: HashMap>>, - pub service_keys_to_statuses_to_display_tags: HashMap>>, -} - #[derive(Clone, Debug)] pub enum FileIdentifier { ID(u64), diff --git a/src/api_core/endpoints/searching_and_fetching_files.rs b/src/api_core/endpoints/searching_and_fetching_files.rs index fe9cdbe..e7324c6 100644 --- a/src/api_core/endpoints/searching_and_fetching_files.rs +++ b/src/api_core/endpoints/searching_and_fetching_files.rs @@ -1,5 +1,10 @@ -use crate::api_core::common::FileMetadataInfo; +use crate::api_core::common::FileMetadataServices; use crate::api_core::endpoints::Endpoint; +use serde::de::DeserializeOwned; +use serde::Deserialize; +use std::collections::HashMap; +use std::fmt::Debug; +use std::marker::PhantomData; pub mod file_sort_type { pub const SORT_FILE_SIZE: u8 = 0; @@ -129,15 +134,15 @@ impl Endpoint for SearchFileHashes { } #[derive(Clone, Debug, Default, Deserialize)] -pub struct FileMetadataResponse { - pub metadata: Vec, +pub struct FileMetadataResponse { + pub metadata: Vec, } -pub struct FileMetadata; +pub struct FileMetadata(PhantomData); -impl Endpoint for FileMetadata { +impl Endpoint for FileMetadata { type Request = (); - type Response = FileMetadataResponse; + type Response = FileMetadataResponse; fn path() -> String { String::from("get_files/file_metadata") @@ -169,3 +174,94 @@ where Self::Tag(s.to_string()) } } + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct FileMetadataIdentifiers { + pub file_id: u64, + pub hash: String, +} + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct FileBasicMetadata { + #[serde(flatten)] + pub identifiers: FileMetadataIdentifiers, + pub size: Option, + pub mime: String, + pub ext: String, + pub width: Option, + pub height: Option, + pub duration: Option, + pub time_modified: Option, + pub file_services: FileMetadataServices, + pub has_audio: Option, + pub num_frames: Option, + pub num_words: Option, +} + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct FileFullMetadata { + #[serde(flatten)] + pub basic_metadata: FileBasicMetadata, + pub is_inbox: bool, + pub is_local: bool, + pub is_trashed: bool, + pub known_urls: Vec, + #[deprecated] + pub service_names_to_statuses_to_tags: HashMap>>, + pub service_keys_to_statuses_to_tags: HashMap>>, + #[deprecated] + pub service_names_to_statuses_to_display_tags: HashMap>>, + pub service_keys_to_statuses_to_display_tags: HashMap>>, +} + +pub trait FileMetadataType: Clone + Debug { + type Response: DeserializeOwned + Clone + Debug; + + fn only_identifiers() -> bool; + fn only_basic_information() -> bool; +} + +#[derive(Clone, Debug)] +pub struct FullMetadata; + +impl FileMetadataType for FullMetadata { + type Response = FileFullMetadata; + + fn only_identifiers() -> bool { + false + } + + fn only_basic_information() -> bool { + false + } +} + +#[derive(Clone, Debug)] +pub struct BasicMetadata; + +impl FileMetadataType for BasicMetadata { + type Response = FileBasicMetadata; + + fn only_identifiers() -> bool { + false + } + + fn only_basic_information() -> bool { + true + } +} + +#[derive(Clone, Debug)] +pub struct Identifiers; + +impl FileMetadataType for Identifiers { + type Response = FileMetadataIdentifiers; + + fn only_identifiers() -> bool { + true + } + + fn only_basic_information() -> bool { + false + } +} diff --git a/src/wrapper/hydrus.rs b/src/wrapper/hydrus.rs index 852c544..52a3294 100644 --- a/src/wrapper/hydrus.rs +++ b/src/wrapper/hydrus.rs @@ -1,4 +1,5 @@ use crate::api_core::common::FileIdentifier; +use crate::api_core::endpoints::searching_and_fetching_files::FullMetadata; use crate::error::Result; use crate::wrapper::address::Address; use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder; @@ -72,7 +73,7 @@ impl Hydrus { pub async fn file(&self, identifier: FileIdentifier) -> Result { let metadata = self .client - .get_file_metadata_by_identifier(identifier) + .get_file_metadata_by_identifier::(identifier) .await?; Ok(HydrusFile::from_metadata(self.client.clone(), metadata)) diff --git a/src/wrapper/hydrus_file.rs b/src/wrapper/hydrus_file.rs index dd5f3f0..ff61b93 100644 --- a/src/wrapper/hydrus_file.rs +++ b/src/wrapper/hydrus_file.rs @@ -1,8 +1,8 @@ use crate::api_core::common::{ - FileIdentifier, FileMetadataInfo, FileRecord, FileSelection, FileServiceSelection, - ServiceIdentifier, + FileIdentifier, FileRecord, FileSelection, FileServiceSelection, ServiceIdentifier, }; use crate::api_core::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; +use crate::api_core::endpoints::searching_and_fetching_files::{FileFullMetadata, FullMetadata}; use crate::error::{Error, Result}; use crate::utils::tag_list_to_string_list; use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder; @@ -39,7 +39,7 @@ pub struct HydrusFile { pub(crate) client: Client, pub id: FileIdentifier, pub status: FileStatus, - pub(crate) metadata: Option, + pub(crate) metadata: Option, } impl HydrusFile { @@ -65,7 +65,7 @@ impl HydrusFile { } } - pub(crate) fn from_metadata(client: Client, metadata: FileMetadataInfo) -> Self { + pub(crate) fn from_metadata(client: Client, metadata: FileFullMetadata) -> Self { let status = if metadata.is_trashed { FileStatus::Deleted } else { @@ -74,7 +74,7 @@ impl HydrusFile { Self { client, - id: FileIdentifier::Hash(metadata.hash.clone()), + id: FileIdentifier::Hash(metadata.basic_metadata.identifiers.hash.clone()), status, metadata: Some(metadata), } @@ -93,7 +93,7 @@ impl HydrusFile { match &self.id { FileIdentifier::ID(_) => { let metadata = self.metadata().await?; - Ok(metadata.hash.clone()) + Ok(metadata.basic_metadata.identifiers.hash.clone()) } FileIdentifier::Hash(hash) => Ok(hash.clone()), } @@ -103,17 +103,18 @@ impl HydrusFile { pub async fn size(&mut self) -> Result> { let metadata = self.metadata().await?; - Ok(metadata.size.clone()) + Ok(metadata.basic_metadata.size.clone()) } /// Returns the mime of the file pub async fn mime(&mut self) -> Result { let metadata = self.metadata().await?; let mime = metadata + .basic_metadata .mime .as_str() .parse() - .map_err(|_| Error::InvalidMime(metadata.mime.clone()))?; + .map_err(|_| Error::InvalidMime(metadata.basic_metadata.mime.clone()))?; Ok(mime) } @@ -122,13 +123,16 @@ impl HydrusFile { pub async fn ext(&mut self) -> Result { let metadata = self.metadata().await?; - Ok(metadata.ext.clone()) + Ok(metadata.basic_metadata.ext.clone()) } /// Returns the dimensions of the file in pixels pub async fn dimensions(&mut self) -> Result> { let metadata = self.metadata().await?; - if let (Some(width), Some(height)) = (&metadata.width, &metadata.height) { + if let (Some(width), Some(height)) = ( + &metadata.basic_metadata.width, + &metadata.basic_metadata.height, + ) { Ok(Some((*width, *height))) } else { Ok(None) @@ -139,21 +143,21 @@ impl HydrusFile { pub async fn duration(&mut self) -> Result> { let metadata = self.metadata().await?; - Ok(metadata.duration.clone()) + Ok(metadata.basic_metadata.duration.clone()) } /// Returns the number of frames of the file if it's a video pub async fn num_frames(&mut self) -> Result> { let metadata = self.metadata().await?; - Ok(metadata.num_frames.clone()) + Ok(metadata.basic_metadata.num_frames.clone()) } /// Returns if the file has audio pub async fn has_audio(&mut self) -> Result { let metadata = self.metadata().await?; - Ok(metadata.has_audio.unwrap_or(false)) + Ok(metadata.basic_metadata.has_audio.unwrap_or(false)) } /// Returns if the file is currently in the inbox @@ -188,6 +192,7 @@ impl HydrusFile { pub async fn time_modified(&mut self) -> Result> { let metadata = self.metadata().await?; let naive_time_modified = metadata + .basic_metadata .time_modified .map(|m| Utc.timestamp_millis(m as i64).naive_utc()); @@ -201,12 +206,14 @@ impl HydrusFile { ) -> Result> { let metadata = self.metadata().await?; let naive_time_imported = metadata + .basic_metadata .file_services .current .get(service_key.as_ref()) .map(|s| s.time_imported) .or_else(|| { metadata + .basic_metadata .file_services .deleted .get(service_key.as_ref()) @@ -224,6 +231,7 @@ impl HydrusFile { ) -> Result> { let metadata = self.metadata().await?; let naive_time_deleted = metadata + .basic_metadata .file_services .deleted .get(service_key.as_ref()) @@ -386,11 +394,11 @@ impl HydrusFile { /// Returns the metadata for the given file /// if there's already known metadata about the file it uses that - async fn metadata(&mut self) -> Result<&FileMetadataInfo> { + async fn metadata(&mut self) -> Result<&FileFullMetadata> { if self.metadata.is_none() { let metadata = self .client - .get_file_metadata_by_identifier(self.id.clone()) + .get_file_metadata_by_identifier::(self.id.clone()) .await?; self.status = if metadata.is_trashed { FileStatus::Deleted diff --git a/src/wrapper/page.rs b/src/wrapper/page.rs index 48f37ea..cc8f68d 100644 --- a/src/wrapper/page.rs +++ b/src/wrapper/page.rs @@ -1,4 +1,5 @@ use crate::api_core::common::{FileIdentifier, PageInformation}; +use crate::api_core::endpoints::searching_and_fetching_files::Identifiers; use crate::error::Result; use crate::utils::split_file_identifiers_into_hashes_and_ids; use crate::Client; @@ -56,7 +57,7 @@ impl HydrusPage { for id in ids { let metadata = self .client - .get_file_metadata_by_identifier(FileIdentifier::ID(id)) + .get_file_metadata_by_identifier::(FileIdentifier::ID(id)) .await?; hashes.push(metadata.hash); } diff --git a/tests/client/test_searching_and_fetching_files.rs b/tests/client/test_searching_and_fetching_files.rs index 9021967..134912b 100644 --- a/tests/client/test_searching_and_fetching_files.rs +++ b/tests/client/test_searching_and_fetching_files.rs @@ -2,7 +2,7 @@ use super::super::common; use hydrus_api::api_core::common::FileIdentifier; use hydrus_api::api_core::endpoints::searching_and_fetching_files::file_sort_type::SORT_FILE_PIXEL_COUNT; use hydrus_api::api_core::endpoints::searching_and_fetching_files::{ - FileSearchOptions, SearchQueryEntry, + BasicMetadata, FileSearchOptions, FullMetadata, Identifiers, SearchQueryEntry, }; #[tokio::test] @@ -47,7 +47,7 @@ async fn is_searches_file_hashes() { async fn it_fetches_file_metadata() { let client = common::get_client(); client - .get_file_metadata( + .get_file_metadata::( vec![], vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()], ) @@ -58,8 +58,18 @@ async fn it_fetches_file_metadata() { #[tokio::test] async fn it_fetches_file_metadata_by_id() { let client = common::get_client(); - let response = client.get_file_metadata(vec![1], vec![]).await; - assert!(response.is_ok()); // Even if the file doesn't exist it still returns some information about it + let response = client + .get_file_metadata::(vec![1], vec![]) + .await; + assert!(response.is_ok()); + let response = client + .get_file_metadata::(vec![1], vec![]) + .await; + assert!(response.is_ok()); + let response = client + .get_file_metadata::(vec![1], vec![]) + .await; + assert!(response.is_ok()); } #[tokio::test]