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]
name = "hydrus-api"
version = "0.8.0"
version = "0.9.0"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"
license = "Apache-2.0"

@ -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<M: FileMetadataType>(
&self,
file_ids: Vec<u64>,
hashes: Vec<String>,
) -> Result<FileMetadataResponse> {
let query = if file_ids.len() > 0 {
) -> Result<FileMetadataResponse<M>> {
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::<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
}
/// 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<M: FileMetadataType>(
&self,
id: FileIdentifier,
) -> Result<FileMetadataInfo> {
) -> Result<M::Response> {
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::<M>(vec![id], vec![]).await?,
FileIdentifier::Hash(hash) => self.get_file_metadata::<M>(vec![], vec![hash]).await?,
};
response

@ -45,33 +45,6 @@ pub struct BasicHashList {
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)]
pub enum FileIdentifier {
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 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<FileMetadataInfo>,
pub struct FileMetadataResponse<M: FileMetadataType> {
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 Response = FileMetadataResponse;
type Response = FileMetadataResponse<M>;
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<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::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<HydrusFile> {
let metadata = self
.client
.get_file_metadata_by_identifier(identifier)
.get_file_metadata_by_identifier::<FullMetadata>(identifier)
.await?;
Ok(HydrusFile::from_metadata(self.client.clone(), metadata))

@ -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<FileMetadataInfo>,
pub(crate) metadata: Option<FileFullMetadata>,
}
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<Option<u64>> {
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<Mime> {
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<String> {
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<Option<(u32, u32)>> {
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<Option<u64>> {
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<Option<u64>> {
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<bool> {
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<Option<NaiveDateTime>> {
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<Option<NaiveDateTime>> {
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<Option<NaiveDateTime>> {
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::<FullMetadata>(self.id.clone())
.await?;
self.status = if metadata.is_trashed {
FileStatus::Deleted

@ -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::<Identifiers>(FileIdentifier::ID(id))
.await?;
hashes.push(metadata.hash);
}

@ -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::<FullMetadata>(
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::<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]

Loading…
Cancel
Save