Add support for only returning basic information or identifiers as metadata

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/15/head
trivernis 3 years ago
parent 715fb9f5f2
commit 843135f38c
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -1,6 +1,6 @@
[package] [package]
name = "hydrus-api" name = "hydrus-api"
version = "0.8.0" version = "0.9.0"
authors = ["trivernis <trivernis@protonmail.com>"] authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018" edition = "2018"
license = "Apache-2.0" license = "Apache-2.0"

@ -1,6 +1,5 @@
use crate::api_core::common::{ use crate::api_core::common::{
FileIdentifier, FileMetadataInfo, FileRecord, FileSelection, FileServiceSelection, FileIdentifier, FileRecord, FileSelection, FileServiceSelection, OptionalStringNumber,
OptionalStringNumber,
}; };
use crate::api_core::endpoints::access_management::{ use crate::api_core::endpoints::access_management::{
ApiVersion, ApiVersionResponse, GetServices, GetServicesResponse, SessionKey, ApiVersion, ApiVersionResponse, GetServices, GetServicesResponse, SessionKey,
@ -30,8 +29,8 @@ use crate::api_core::endpoints::managing_pages::{
GetPages, GetPagesResponse, GetPages, GetPagesResponse,
}; };
use crate::api_core::endpoints::searching_and_fetching_files::{ use crate::api_core::endpoints::searching_and_fetching_files::{
FileMetadata, FileMetadataResponse, FileSearchOptions, GetFile, SearchFileHashes, FileMetadata, FileMetadataResponse, FileMetadataType, FileSearchOptions, GetFile,
SearchFileHashesResponse, SearchFiles, SearchFilesResponse, SearchQueryEntry, SearchFileHashes, SearchFileHashesResponse, SearchFiles, SearchFilesResponse, SearchQueryEntry,
}; };
use crate::api_core::endpoints::Endpoint; use crate::api_core::endpoints::Endpoint;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
@ -224,29 +223,40 @@ impl Client {
/// Returns the metadata for a given list of file_ids or hashes /// Returns the metadata for a given list of file_ids or hashes
#[tracing::instrument(skip(self), level = "debug")] #[tracing::instrument(skip(self), level = "debug")]
pub async fn get_file_metadata( pub async fn get_file_metadata<M: FileMetadataType>(
&self, &self,
file_ids: Vec<u64>, file_ids: Vec<u64>,
hashes: Vec<String>, hashes: Vec<String>,
) -> Result<FileMetadataResponse> { ) -> Result<FileMetadataResponse<M>> {
let query = if file_ids.len() > 0 { let id_query = if file_ids.len() > 0 {
("file_ids", Self::serialize_query_object(file_ids)?) ("file_ids", Self::serialize_query_object(file_ids)?)
} else { } else {
("hashes", Self::serialize_query_object(hashes)?) ("hashes", Self::serialize_query_object(hashes)?)
}; };
self.get_and_parse::<FileMetadata, [(&str, String)]>(&[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::<FileMetadata<M>, [(&str, String)]>(&query)
.await .await
} }
/// Returns the metadata for a single file identifier /// Returns the metadata for a single file identifier
#[tracing::instrument(skip(self), level = "debug")] #[tracing::instrument(skip(self), level = "debug")]
pub async fn get_file_metadata_by_identifier( pub async fn get_file_metadata_by_identifier<M: FileMetadataType>(
&self, &self,
id: FileIdentifier, id: FileIdentifier,
) -> Result<FileMetadataInfo> { ) -> Result<M::Response> {
let mut response = match id.clone() { let mut response = match id.clone() {
FileIdentifier::ID(id) => self.get_file_metadata(vec![id], vec![]).await?, FileIdentifier::ID(id) => self.get_file_metadata::<M>(vec![id], vec![]).await?,
FileIdentifier::Hash(hash) => self.get_file_metadata(vec![], vec![hash]).await?, FileIdentifier::Hash(hash) => self.get_file_metadata::<M>(vec![], vec![hash]).await?,
}; };
response response

@ -45,33 +45,6 @@ pub struct BasicHashList {
pub hashes: Vec<String>, pub hashes: Vec<String>,
} }
#[derive(Clone, Debug, Default, Deserialize)]
pub struct FileMetadataInfo {
pub file_id: u64,
pub hash: String,
pub size: Option<u64>,
pub mime: String,
pub ext: String,
pub width: Option<u32>,
pub height: Option<u32>,
pub duration: Option<u64>,
pub time_modified: Option<u64>,
pub file_services: FileMetadataServices,
pub has_audio: Option<bool>,
pub num_frames: Option<u64>,
pub num_words: Option<u64>,
pub is_inbox: bool,
pub is_local: bool,
pub is_trashed: bool,
pub known_urls: Vec<String>,
#[deprecated]
pub service_names_to_statuses_to_tags: HashMap<String, HashMap<String, Vec<String>>>,
pub service_keys_to_statuses_to_tags: HashMap<String, HashMap<String, Vec<String>>>,
#[deprecated]
pub service_names_to_statuses_to_display_tags: HashMap<String, HashMap<String, Vec<String>>>,
pub service_keys_to_statuses_to_display_tags: HashMap<String, HashMap<String, Vec<String>>>,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum FileIdentifier { pub enum FileIdentifier {
ID(u64), ID(u64),

@ -1,5 +1,10 @@
use crate::api_core::common::FileMetadataInfo; use crate::api_core::common::FileMetadataServices;
use crate::api_core::endpoints::Endpoint; 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 mod file_sort_type {
pub const SORT_FILE_SIZE: u8 = 0; pub const SORT_FILE_SIZE: u8 = 0;
@ -129,15 +134,15 @@ impl Endpoint for SearchFileHashes {
} }
#[derive(Clone, Debug, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
pub struct FileMetadataResponse { pub struct FileMetadataResponse<M: FileMetadataType> {
pub metadata: Vec<FileMetadataInfo>, pub metadata: Vec<M::Response>,
} }
pub struct FileMetadata; pub struct FileMetadata<M: FileMetadataType>(PhantomData<M>);
impl Endpoint for FileMetadata { impl<M: FileMetadataType> Endpoint for FileMetadata<M> {
type Request = (); type Request = ();
type Response = FileMetadataResponse; type Response = FileMetadataResponse<M>;
fn path() -> String { fn path() -> String {
String::from("get_files/file_metadata") String::from("get_files/file_metadata")
@ -169,3 +174,94 @@ where
Self::Tag(s.to_string()) 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<u64>,
pub mime: String,
pub ext: String,
pub width: Option<u32>,
pub height: Option<u32>,
pub duration: Option<u64>,
pub time_modified: Option<u64>,
pub file_services: FileMetadataServices,
pub has_audio: Option<bool>,
pub num_frames: Option<u64>,
pub num_words: Option<u64>,
}
#[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<String>,
#[deprecated]
pub service_names_to_statuses_to_tags: HashMap<String, HashMap<String, Vec<String>>>,
pub service_keys_to_statuses_to_tags: HashMap<String, HashMap<String, Vec<String>>>,
#[deprecated]
pub service_names_to_statuses_to_display_tags: HashMap<String, HashMap<String, Vec<String>>>,
pub service_keys_to_statuses_to_display_tags: HashMap<String, HashMap<String, Vec<String>>>,
}
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
}
}

@ -1,4 +1,5 @@
use crate::api_core::common::FileIdentifier; use crate::api_core::common::FileIdentifier;
use crate::api_core::endpoints::searching_and_fetching_files::FullMetadata;
use crate::error::Result; use crate::error::Result;
use crate::wrapper::address::Address; use crate::wrapper::address::Address;
use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder; use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder;
@ -72,7 +73,7 @@ impl Hydrus {
pub async fn file(&self, identifier: FileIdentifier) -> Result<HydrusFile> { pub async fn file(&self, identifier: FileIdentifier) -> Result<HydrusFile> {
let metadata = self let metadata = self
.client .client
.get_file_metadata_by_identifier(identifier) .get_file_metadata_by_identifier::<FullMetadata>(identifier)
.await?; .await?;
Ok(HydrusFile::from_metadata(self.client.clone(), metadata)) Ok(HydrusFile::from_metadata(self.client.clone(), metadata))

@ -1,8 +1,8 @@
use crate::api_core::common::{ use crate::api_core::common::{
FileIdentifier, FileMetadataInfo, FileRecord, FileSelection, FileServiceSelection, FileIdentifier, FileRecord, FileSelection, FileServiceSelection, ServiceIdentifier,
ServiceIdentifier,
}; };
use crate::api_core::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; 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::error::{Error, Result};
use crate::utils::tag_list_to_string_list; use crate::utils::tag_list_to_string_list;
use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder; use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder;
@ -39,7 +39,7 @@ pub struct HydrusFile {
pub(crate) client: Client, pub(crate) client: Client,
pub id: FileIdentifier, pub id: FileIdentifier,
pub status: FileStatus, pub status: FileStatus,
pub(crate) metadata: Option<FileMetadataInfo>, pub(crate) metadata: Option<FileFullMetadata>,
} }
impl HydrusFile { 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 { let status = if metadata.is_trashed {
FileStatus::Deleted FileStatus::Deleted
} else { } else {
@ -74,7 +74,7 @@ impl HydrusFile {
Self { Self {
client, client,
id: FileIdentifier::Hash(metadata.hash.clone()), id: FileIdentifier::Hash(metadata.basic_metadata.identifiers.hash.clone()),
status, status,
metadata: Some(metadata), metadata: Some(metadata),
} }
@ -93,7 +93,7 @@ impl HydrusFile {
match &self.id { match &self.id {
FileIdentifier::ID(_) => { FileIdentifier::ID(_) => {
let metadata = self.metadata().await?; let metadata = self.metadata().await?;
Ok(metadata.hash.clone()) Ok(metadata.basic_metadata.identifiers.hash.clone())
} }
FileIdentifier::Hash(hash) => Ok(hash.clone()), FileIdentifier::Hash(hash) => Ok(hash.clone()),
} }
@ -103,17 +103,18 @@ impl HydrusFile {
pub async fn size(&mut self) -> Result<Option<u64>> { pub async fn size(&mut self) -> Result<Option<u64>> {
let metadata = self.metadata().await?; let metadata = self.metadata().await?;
Ok(metadata.size.clone()) Ok(metadata.basic_metadata.size.clone())
} }
/// Returns the mime of the file /// Returns the mime of the file
pub async fn mime(&mut self) -> Result<Mime> { pub async fn mime(&mut self) -> Result<Mime> {
let metadata = self.metadata().await?; let metadata = self.metadata().await?;
let mime = metadata let mime = metadata
.basic_metadata
.mime .mime
.as_str() .as_str()
.parse() .parse()
.map_err(|_| Error::InvalidMime(metadata.mime.clone()))?; .map_err(|_| Error::InvalidMime(metadata.basic_metadata.mime.clone()))?;
Ok(mime) Ok(mime)
} }
@ -122,13 +123,16 @@ impl HydrusFile {
pub async fn ext(&mut self) -> Result<String> { pub async fn ext(&mut self) -> Result<String> {
let metadata = self.metadata().await?; let metadata = self.metadata().await?;
Ok(metadata.ext.clone()) Ok(metadata.basic_metadata.ext.clone())
} }
/// Returns the dimensions of the file in pixels /// Returns the dimensions of the file in pixels
pub async fn dimensions(&mut self) -> Result<Option<(u32, u32)>> { pub async fn dimensions(&mut self) -> Result<Option<(u32, u32)>> {
let metadata = self.metadata().await?; 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))) Ok(Some((*width, *height)))
} else { } else {
Ok(None) Ok(None)
@ -139,21 +143,21 @@ impl HydrusFile {
pub async fn duration(&mut self) -> Result<Option<u64>> { pub async fn duration(&mut self) -> Result<Option<u64>> {
let metadata = self.metadata().await?; 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 /// Returns the number of frames of the file if it's a video
pub async fn num_frames(&mut self) -> Result<Option<u64>> { pub async fn num_frames(&mut self) -> Result<Option<u64>> {
let metadata = self.metadata().await?; let metadata = self.metadata().await?;
Ok(metadata.num_frames.clone()) Ok(metadata.basic_metadata.num_frames.clone())
} }
/// Returns if the file has audio /// Returns if the file has audio
pub async fn has_audio(&mut self) -> Result<bool> { pub async fn has_audio(&mut self) -> Result<bool> {
let metadata = self.metadata().await?; 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 /// Returns if the file is currently in the inbox
@ -188,6 +192,7 @@ impl HydrusFile {
pub async fn time_modified(&mut self) -> Result<Option<NaiveDateTime>> { pub async fn time_modified(&mut self) -> Result<Option<NaiveDateTime>> {
let metadata = self.metadata().await?; let metadata = self.metadata().await?;
let naive_time_modified = metadata let naive_time_modified = metadata
.basic_metadata
.time_modified .time_modified
.map(|m| Utc.timestamp_millis(m as i64).naive_utc()); .map(|m| Utc.timestamp_millis(m as i64).naive_utc());
@ -201,12 +206,14 @@ impl HydrusFile {
) -> Result<Option<NaiveDateTime>> { ) -> Result<Option<NaiveDateTime>> {
let metadata = self.metadata().await?; let metadata = self.metadata().await?;
let naive_time_imported = metadata let naive_time_imported = metadata
.basic_metadata
.file_services .file_services
.current .current
.get(service_key.as_ref()) .get(service_key.as_ref())
.map(|s| s.time_imported) .map(|s| s.time_imported)
.or_else(|| { .or_else(|| {
metadata metadata
.basic_metadata
.file_services .file_services
.deleted .deleted
.get(service_key.as_ref()) .get(service_key.as_ref())
@ -224,6 +231,7 @@ impl HydrusFile {
) -> Result<Option<NaiveDateTime>> { ) -> Result<Option<NaiveDateTime>> {
let metadata = self.metadata().await?; let metadata = self.metadata().await?;
let naive_time_deleted = metadata let naive_time_deleted = metadata
.basic_metadata
.file_services .file_services
.deleted .deleted
.get(service_key.as_ref()) .get(service_key.as_ref())
@ -386,11 +394,11 @@ impl HydrusFile {
/// Returns the metadata for the given file /// Returns the metadata for the given file
/// if there's already known metadata about the file it uses that /// 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() { if self.metadata.is_none() {
let metadata = self let metadata = self
.client .client
.get_file_metadata_by_identifier(self.id.clone()) .get_file_metadata_by_identifier::<FullMetadata>(self.id.clone())
.await?; .await?;
self.status = if metadata.is_trashed { self.status = if metadata.is_trashed {
FileStatus::Deleted FileStatus::Deleted

@ -1,4 +1,5 @@
use crate::api_core::common::{FileIdentifier, PageInformation}; use crate::api_core::common::{FileIdentifier, PageInformation};
use crate::api_core::endpoints::searching_and_fetching_files::Identifiers;
use crate::error::Result; use crate::error::Result;
use crate::utils::split_file_identifiers_into_hashes_and_ids; use crate::utils::split_file_identifiers_into_hashes_and_ids;
use crate::Client; use crate::Client;
@ -56,7 +57,7 @@ impl HydrusPage {
for id in ids { for id in ids {
let metadata = self let metadata = self
.client .client
.get_file_metadata_by_identifier(FileIdentifier::ID(id)) .get_file_metadata_by_identifier::<Identifiers>(FileIdentifier::ID(id))
.await?; .await?;
hashes.push(metadata.hash); hashes.push(metadata.hash);
} }

@ -2,7 +2,7 @@ use super::super::common;
use hydrus_api::api_core::common::FileIdentifier; 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::file_sort_type::SORT_FILE_PIXEL_COUNT;
use hydrus_api::api_core::endpoints::searching_and_fetching_files::{ use hydrus_api::api_core::endpoints::searching_and_fetching_files::{
FileSearchOptions, SearchQueryEntry, BasicMetadata, FileSearchOptions, FullMetadata, Identifiers, SearchQueryEntry,
}; };
#[tokio::test] #[tokio::test]
@ -47,7 +47,7 @@ async fn is_searches_file_hashes() {
async fn it_fetches_file_metadata() { async fn it_fetches_file_metadata() {
let client = common::get_client(); let client = common::get_client();
client client
.get_file_metadata( .get_file_metadata::<FullMetadata>(
vec![], vec![],
vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()], vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()],
) )
@ -58,8 +58,18 @@ async fn it_fetches_file_metadata() {
#[tokio::test] #[tokio::test]
async fn it_fetches_file_metadata_by_id() { async fn it_fetches_file_metadata_by_id() {
let client = common::get_client(); let client = common::get_client();
let response = client.get_file_metadata(vec![1], vec![]).await; let response = client
assert!(response.is_ok()); // Even if the file doesn't exist it still returns some information about it .get_file_metadata::<Identifiers>(vec![1], vec![])
.await;
assert!(response.is_ok());
let response = client
.get_file_metadata::<BasicMetadata>(vec![1], vec![])
.await;
assert!(response.is_ok());
let response = client
.get_file_metadata::<FullMetadata>(vec![1], vec![])
.await;
assert!(response.is_ok());
} }
#[tokio::test] #[tokio::test]

Loading…
Cancel
Save