Add searching and fetching of files

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/2/head
trivernis 3 years ago
parent 2c5b3225f0
commit 5563d61155
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -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<E: Endpoint, Q: Serialize + ?Sized>(
&mut self,
query: &Q,
) -> Result<E::Response> {
/// Starts a get request to the path
async fn get<E: Endpoint, Q: Serialize + ?Sized>(&mut self, query: &Q) -> Result<Response> {
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<E: Endpoint, Q: Serialize + ?Sized>(
&mut self,
query: &Q,
) -> Result<E::Response> {
let response = self.get::<E, Q>(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<E: Endpoint>(&mut self, body: E::Request) -> Result<Response> {
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<String>,
location: FileSearchLocation,
) -> Result<SearchFilesResponse> {
self.get_and_parse::<SearchFiles, [(&str, String)]>(&[
("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<u64>,
hashes: Vec<String>,
) -> Result<FileMetadataResponse> {
self.get_and_parse::<FileMetadata, [(&str, String)]>(&[
("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<FileRecord> {
let response = match id {
FileIdentifier::ID(id) => {
self.get::<GetFile, [(&str, u64)]>(&[("file_id", id)])
.await?
}
FileIdentifier::Hash(hash) => {
self.get::<GetFile, [(&str, String)]>(&[("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 })
}
}

@ -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<String>,
}
#[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<u64>,
pub has_audio: bool,
pub num_frames: Option<u16>,
pub num_words: Option<u64>,
pub is_inbox: bool,
pub is_local: bool,
pub is_trashed: bool,
pub known_urls: Vec<String>,
pub service_names_to_statuses_to_tags: HashMap<String, HashMap<String, Vec<String>>>,
pub service_names_to_statuses_to_display_tags: HashMap<String, HashMap<String, Vec<String>>>,
}
pub enum FileIdentifier {
ID(u64),
Hash(String),
}
pub struct FileRecord {
pub bytes: Vec<u8>,
pub mime_type: String,
}

@ -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;

@ -0,0 +1,77 @@
use crate::endpoints::common::FileMetadataInfo;
use crate::endpoints::Endpoint;
#[derive(Debug, Clone, Deserialize)]
pub struct SearchFilesResponse {
pub file_ids: Vec<u64>,
}
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<FileMetadataInfo>,
}
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")
}
}

@ -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;

@ -1,3 +1,14 @@
pub fn string_list_to_json_array(l: Vec<String>) -> String {
format!("[\"{}\"]", l.join("\",\""))
}
pub fn number_list_to_json_array<T: ToString>(l: Vec<T>) -> String {
format!(
"[{}]",
l.into_iter().fold(String::from(""), |acc, val| format!(
"{},{}",
acc,
val.to_string()
))
)
}

@ -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
}
Loading…
Cancel
Save