Add functions to files to retrieve information, associate urls and get tags

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/1/head
trivernis 3 years ago
parent 188da6c4c1
commit 7d6b2bfe96
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -11,7 +11,7 @@ use crate::endpoints::adding_urls::{
AddUrl, AddUrlRequest, AddUrlResponse, AssociateUrl, AssociateUrlRequest, GetUrlFiles, AddUrl, AddUrlRequest, AddUrlResponse, AssociateUrl, AssociateUrlRequest, GetUrlFiles,
GetUrlFilesResponse, GetUrlInfo, GetUrlInfoResponse, GetUrlFilesResponse, GetUrlInfo, GetUrlInfoResponse,
}; };
use crate::endpoints::common::{FileIdentifier, FileRecord}; use crate::endpoints::common::{FileIdentifier, FileMetadataInfo, FileRecord};
use crate::endpoints::searching_and_fetching_files::{ use crate::endpoints::searching_and_fetching_files::{
FileMetadata, FileMetadataResponse, FileSearchLocation, GetFile, SearchFiles, FileMetadata, FileMetadataResponse, FileSearchLocation, GetFile, SearchFiles,
SearchFilesResponse, SearchFilesResponse,
@ -43,10 +43,10 @@ impl Client {
} }
/// Starts a get request to the path /// Starts a get request to the path
async fn get<E: Endpoint, Q: Serialize + ?Sized>(&mut self, query: &Q) -> Result<Response> { async fn get<E: Endpoint, Q: Serialize + ?Sized>(&self, query: &Q) -> Result<Response> {
let response = self let response = self
.inner .inner
.get(format!("{}/{}", self.base_url, E::get_path())) .get(format!("{}/{}", self.base_url, E::path()))
.header(ACCESS_KEY_HEADER, &self.access_key) .header(ACCESS_KEY_HEADER, &self.access_key)
.query(query) .query(query)
.send() .send()
@ -57,7 +57,7 @@ impl Client {
/// Starts a get request to the path associated with the Endpoint Type /// Starts a get request to the path associated with the Endpoint Type
async fn get_and_parse<E: Endpoint, Q: Serialize + ?Sized>( async fn get_and_parse<E: Endpoint, Q: Serialize + ?Sized>(
&mut self, &self,
query: &Q, query: &Q,
) -> Result<E::Response> { ) -> Result<E::Response> {
let response = self.get::<E, Q>(query).await?; let response = self.get::<E, Q>(query).await?;
@ -66,10 +66,10 @@ impl Client {
} }
/// Stats a post request to the path associated with the Endpoint 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> { async fn post<E: Endpoint>(&self, body: E::Request) -> Result<Response> {
let response = self let response = self
.inner .inner
.post(format!("{}/{}", self.base_url, E::get_path())) .post(format!("{}/{}", self.base_url, E::path()))
.json(&body) .json(&body)
.header(ACCESS_KEY_HEADER, &self.access_key) .header(ACCESS_KEY_HEADER, &self.access_key)
.send() .send()
@ -79,17 +79,17 @@ impl Client {
} }
/// Stats a post request and parses the body as json /// Stats a post request and parses the body as json
async fn post_and_parse<E: Endpoint>(&mut self, body: E::Request) -> Result<E::Response> { async fn post_and_parse<E: Endpoint>(&self, body: E::Request) -> Result<E::Response> {
let response = self.post::<E>(body).await?; let response = self.post::<E>(body).await?;
Self::extract_content(response).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 return type
async fn post_binary<E: Endpoint>(&mut self, data: Vec<u8>) -> Result<E::Response> { async fn post_binary<E: Endpoint>(&self, data: Vec<u8>) -> Result<E::Response> {
let response = self let response = self
.inner .inner
.post(format!("{}/{}", self.base_url, E::get_path())) .post(format!("{}/{}", self.base_url, E::path()))
.body(data) .body(data)
.header(ACCESS_KEY_HEADER, &self.access_key) .header(ACCESS_KEY_HEADER, &self.access_key)
.header("Content-Type", "application/octet-stream") .header("Content-Type", "application/octet-stream")
@ -116,27 +116,27 @@ impl Client {
} }
/// Returns the current API version. It's being incremented every time the API changes. /// Returns the current API version. It's being incremented every time the API changes.
pub async fn api_version(&mut self) -> Result<ApiVersionResponse> { pub async fn api_version(&self) -> Result<ApiVersionResponse> {
self.get_and_parse::<ApiVersion, ()>(&()).await self.get_and_parse::<ApiVersion, ()>(&()).await
} }
/// Creates a new session key /// Creates a new session key
pub async fn session_key(&mut self) -> Result<SessionKeyResponse> { pub async fn session_key(&self) -> Result<SessionKeyResponse> {
self.get_and_parse::<SessionKey, ()>(&()).await self.get_and_parse::<SessionKey, ()>(&()).await
} }
/// Verifies if the access key is valid and returns some information about its permissions /// Verifies if the access key is valid and returns some information about its permissions
pub async fn verify_access_key(&mut self) -> Result<VerifyAccessKeyResponse> { pub async fn verify_access_key(&self) -> Result<VerifyAccessKeyResponse> {
self.get_and_parse::<VerifyAccessKey, ()>(&()).await self.get_and_parse::<VerifyAccessKey, ()>(&()).await
} }
/// Returns the list of tag and file services of the client /// Returns the list of tag and file services of the client
pub async fn get_services(&mut self) -> Result<GetServicesResponse> { pub async fn get_services(&self) -> Result<GetServicesResponse> {
self.get_and_parse::<GetServices, ()>(&()).await self.get_and_parse::<GetServices, ()>(&()).await
} }
/// Adds a file to hydrus /// Adds a file to hydrus
pub async fn add_file<S: AsRef<str>>(&mut self, path: S) -> Result<AddFileResponse> { pub async fn add_file<S: AsRef<str>>(&self, path: S) -> Result<AddFileResponse> {
self.post_and_parse::<AddFile>(AddFileRequest { self.post_and_parse::<AddFile>(AddFileRequest {
path: path.as_ref().to_string(), path: path.as_ref().to_string(),
}) })
@ -144,12 +144,12 @@ impl Client {
} }
/// Adds a file from binary data to hydrus /// Adds a file from binary data to hydrus
pub async fn add_binary_file(&mut self, data: Vec<u8>) -> Result<AddFileResponse> { pub async fn add_binary_file(&self, data: Vec<u8>) -> Result<AddFileResponse> {
self.post_binary::<AddFile>(data).await self.post_binary::<AddFile>(data).await
} }
/// Moves files with matching hashes to the trash /// Moves files with matching hashes to the trash
pub async fn delete_files(&mut self, hashes: Vec<String>) -> Result<()> { pub async fn delete_files(&self, hashes: Vec<String>) -> Result<()> {
self.post::<DeleteFiles>(DeleteFilesRequest { hashes }) self.post::<DeleteFiles>(DeleteFilesRequest { hashes })
.await?; .await?;
@ -157,7 +157,7 @@ impl Client {
} }
/// Pulls files out of the trash by hash /// Pulls files out of the trash by hash
pub async fn undelete_files(&mut self, hashes: Vec<String>) -> Result<()> { pub async fn undelete_files(&self, hashes: Vec<String>) -> Result<()> {
self.post::<UndeleteFiles>(UndeleteFilesRequest { hashes }) self.post::<UndeleteFiles>(UndeleteFilesRequest { hashes })
.await?; .await?;
@ -165,7 +165,7 @@ impl Client {
} }
/// Moves files from the inbox into the archive /// Moves files from the inbox into the archive
pub async fn archive_files(&mut self, hashes: Vec<String>) -> Result<()> { pub async fn archive_files(&self, hashes: Vec<String>) -> Result<()> {
self.post::<ArchiveFiles>(ArchiveFilesRequest { hashes }) self.post::<ArchiveFiles>(ArchiveFilesRequest { hashes })
.await?; .await?;
@ -173,7 +173,7 @@ impl Client {
} }
/// Moves files from the archive into the inbox /// Moves files from the archive into the inbox
pub async fn unarchive_files(&mut self, hashes: Vec<String>) -> Result<()> { pub async fn unarchive_files(&self, hashes: Vec<String>) -> Result<()> {
self.post::<UnarchiveFiles>(UnarchiveFilesRequest { hashes }) self.post::<UnarchiveFiles>(UnarchiveFilesRequest { hashes })
.await?; .await?;
@ -181,7 +181,7 @@ impl Client {
} }
/// Returns the list of tags as the client would see them in a human friendly order /// Returns the list of tags as the client would see them in a human friendly order
pub async fn clean_tags(&mut self, tags: Vec<String>) -> Result<CleanTagsResponse> { pub async fn clean_tags(&self, tags: Vec<String>) -> Result<CleanTagsResponse> {
self.get_and_parse::<CleanTags, [(&str, String)]>(&[( self.get_and_parse::<CleanTags, [(&str, String)]>(&[(
"tags", "tags",
string_list_to_json_array(tags), string_list_to_json_array(tags),
@ -190,7 +190,7 @@ impl Client {
} }
/// Adds tags to files with the given hashes /// Adds tags to files with the given hashes
pub async fn add_tags(&mut self, request: AddTagsRequest) -> Result<()> { pub async fn add_tags(&self, request: AddTagsRequest) -> Result<()> {
self.post::<AddTags>(request).await?; self.post::<AddTags>(request).await?;
Ok(()) Ok(())
@ -198,7 +198,7 @@ impl Client {
/// Searches for files in the inbox, the archive or both /// Searches for files in the inbox, the archive or both
pub async fn search_files( pub async fn search_files(
&mut self, &self,
tags: Vec<String>, tags: Vec<String>,
location: FileSearchLocation, location: FileSearchLocation,
) -> Result<SearchFilesResponse> { ) -> Result<SearchFilesResponse> {
@ -212,19 +212,37 @@ 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
pub async fn get_file_metadata( pub async fn get_file_metadata(
&mut self, &self,
file_ids: Vec<u64>, file_ids: Vec<u64>,
hashes: Vec<String>, hashes: Vec<String>,
) -> Result<FileMetadataResponse> { ) -> Result<FileMetadataResponse> {
self.get_and_parse::<FileMetadata, [(&str, String)]>(&[ let query = if file_ids.len() > 0 {
("file_ids", number_list_to_json_array(file_ids)), ("file_ids", number_list_to_json_array(file_ids))
("hashes", string_list_to_json_array(hashes)), } else {
]) ("hashes", string_list_to_json_array(hashes))
.await };
self.get_and_parse::<FileMetadata, [(&str, String)]>(&[query])
.await
}
/// Returns the metadata for a single file identifier
pub async fn get_file_metadata_by_identifier(
&self,
identifier: FileIdentifier,
) -> Result<FileMetadataInfo> {
let mut response = match identifier.clone() {
FileIdentifier::ID(id) => self.get_file_metadata(vec![id], vec![]).await?,
FileIdentifier::Hash(hash) => self.get_file_metadata(vec![], vec![hash]).await?,
};
response
.metadata
.pop()
.ok_or_else(|| Error::FileNotFound(identifier))
} }
/// Returns the bytes of a file from hydrus /// Returns the bytes of a file from hydrus
pub async fn get_file(&mut self, id: FileIdentifier) -> Result<FileRecord> { pub async fn get_file(&self, id: FileIdentifier) -> Result<FileRecord> {
let response = match id { let response = match id {
FileIdentifier::ID(id) => { FileIdentifier::ID(id) => {
self.get::<GetFile, [(&str, u64)]>(&[("file_id", id)]) self.get::<GetFile, [(&str, u64)]>(&[("file_id", id)])
@ -248,24 +266,24 @@ impl Client {
} }
/// Returns all files associated with the given url /// Returns all files associated with the given url
pub async fn get_url_files<S: AsRef<str>>(&mut self, url: S) -> Result<GetUrlFilesResponse> { pub async fn get_url_files<S: AsRef<str>>(&self, url: S) -> Result<GetUrlFilesResponse> {
self.get_and_parse::<GetUrlFiles, [(&str, &str)]>(&[("url", url.as_ref())]) self.get_and_parse::<GetUrlFiles, [(&str, &str)]>(&[("url", url.as_ref())])
.await .await
} }
/// Returns information about the given url /// Returns information about the given url
pub async fn get_url_info<S: AsRef<str>>(&mut self, url: S) -> Result<GetUrlInfoResponse> { pub async fn get_url_info<S: AsRef<str>>(&self, url: S) -> Result<GetUrlInfoResponse> {
self.get_and_parse::<GetUrlInfo, [(&str, &str)]>(&[("url", url.as_ref())]) self.get_and_parse::<GetUrlInfo, [(&str, &str)]>(&[("url", url.as_ref())])
.await .await
} }
/// Adds an url to hydrus, optionally with additional tags and a destination page /// Adds an url to hydrus, optionally with additional tags and a destination page
pub async fn add_url(&mut self, request: AddUrlRequest) -> Result<AddUrlResponse> { pub async fn add_url(&self, request: AddUrlRequest) -> Result<AddUrlResponse> {
self.post_and_parse::<AddUrl>(request).await self.post_and_parse::<AddUrl>(request).await
} }
/// Associates urls with the given file hashes /// Associates urls with the given file hashes
pub async fn associate_urls(&mut self, urls: Vec<String>, hashes: Vec<String>) -> Result<()> { pub async fn associate_urls(&self, urls: Vec<String>, hashes: Vec<String>) -> Result<()> {
self.post::<AssociateUrl>(AssociateUrlRequest { self.post::<AssociateUrl>(AssociateUrlRequest {
hashes, hashes,
urls_to_add: urls, urls_to_add: urls,
@ -277,11 +295,7 @@ impl Client {
} }
/// Disassociates urls with the given file hashes /// Disassociates urls with the given file hashes
pub async fn disassociate_urls( pub async fn disassociate_urls(&self, urls: Vec<String>, hashes: Vec<String>) -> Result<()> {
&mut self,
urls: Vec<String>,
hashes: Vec<String>,
) -> Result<()> {
self.post::<AssociateUrl>(AssociateUrlRequest { self.post::<AssociateUrl>(AssociateUrlRequest {
hashes, hashes,
urls_to_add: vec![], urls_to_add: vec![],

@ -23,7 +23,7 @@ impl Endpoint for ApiVersion {
type Request = (); type Request = ();
type Response = ApiVersionResponse; type Response = ApiVersionResponse;
fn get_path() -> String { fn path() -> String {
String::from("api_version") String::from("api_version")
} }
} }
@ -39,7 +39,7 @@ impl Endpoint for SessionKey {
type Request = (); type Request = ();
type Response = SessionKeyResponse; type Response = SessionKeyResponse;
fn get_path() -> String { fn path() -> String {
String::from("session_key") String::from("session_key")
} }
} }
@ -56,7 +56,7 @@ impl Endpoint for VerifyAccessKey {
type Request = (); type Request = ();
type Response = VerifyAccessKeyResponse; type Response = VerifyAccessKeyResponse;
fn get_path() -> String { fn path() -> String {
String::from("verify_access_key") String::from("verify_access_key")
} }
} }
@ -70,7 +70,7 @@ impl Endpoint for GetServices {
type Request = (); type Request = ();
type Response = GetServicesResponse; type Response = GetServicesResponse;
fn get_path() -> String { fn path() -> String {
String::from("get_services") String::from("get_services")
} }
} }

@ -25,7 +25,7 @@ impl Endpoint for AddFile {
type Request = AddFileRequest; type Request = AddFileRequest;
type Response = AddFileResponse; type Response = AddFileResponse;
fn get_path() -> String { fn path() -> String {
String::from("add_files/add_file") String::from("add_files/add_file")
} }
} }
@ -38,7 +38,7 @@ impl Endpoint for DeleteFiles {
type Request = DeleteFilesRequest; type Request = DeleteFilesRequest;
type Response = (); type Response = ();
fn get_path() -> String { fn path() -> String {
String::from("add_files/delete_files") String::from("add_files/delete_files")
} }
} }
@ -50,7 +50,7 @@ impl Endpoint for UndeleteFiles {
type Request = UndeleteFilesRequest; type Request = UndeleteFilesRequest;
type Response = (); type Response = ();
fn get_path() -> String { fn path() -> String {
String::from("add_files/undelete_files") String::from("add_files/undelete_files")
} }
} }
@ -62,7 +62,7 @@ impl Endpoint for ArchiveFiles {
type Request = ArchiveFilesRequest; type Request = ArchiveFilesRequest;
type Response = (); type Response = ();
fn get_path() -> String { fn path() -> String {
String::from("add_files/archive_files") String::from("add_files/archive_files")
} }
} }
@ -74,7 +74,7 @@ impl Endpoint for UnarchiveFiles {
type Request = UndeleteFilesRequest; type Request = UndeleteFilesRequest;
type Response = (); type Response = ();
fn get_path() -> String { fn path() -> String {
String::from("add_files/unarchive_files") String::from("add_files/unarchive_files")
} }
} }

@ -12,7 +12,7 @@ impl Endpoint for CleanTags {
type Request = (); type Request = ();
type Response = CleanTagsResponse; type Response = CleanTagsResponse;
fn get_path() -> String { fn path() -> String {
String::from("add_tags/clean_tags") String::from("add_tags/clean_tags")
} }
} }
@ -30,7 +30,7 @@ impl Endpoint for AddTags {
type Request = AddTagsRequest; type Request = AddTagsRequest;
type Response = (); type Response = ();
fn get_path() -> String { fn path() -> String {
String::from("add_tags/add_tags") String::from("add_tags/add_tags")
} }
} }

@ -27,7 +27,7 @@ impl Endpoint for GetUrlFiles {
type Request = (); type Request = ();
type Response = GetUrlFilesResponse; type Response = GetUrlFilesResponse;
fn get_path() -> String { fn path() -> String {
String::from("add_urls/get_url_files") String::from("add_urls/get_url_files")
} }
} }
@ -47,7 +47,7 @@ impl Endpoint for GetUrlInfo {
type Request = (); type Request = ();
type Response = GetUrlInfoResponse; type Response = GetUrlInfoResponse;
fn get_path() -> String { fn path() -> String {
String::from("add_urls/get_url_info") String::from("add_urls/get_url_info")
} }
} }
@ -164,7 +164,7 @@ impl Endpoint for AddUrl {
type Request = AddUrlRequest; type Request = AddUrlRequest;
type Response = AddUrlResponse; type Response = AddUrlResponse;
fn get_path() -> String { fn path() -> String {
String::from("add_urls/add_url") String::from("add_urls/add_url")
} }
} }
@ -181,7 +181,7 @@ impl Endpoint for AssociateUrl {
type Request = AssociateUrlRequest; type Request = AssociateUrlRequest;
type Response = (); type Response = ();
fn get_path() -> String { fn path() -> String {
String::from("add_urls/associate_url") String::from("add_urls/associate_url")
} }
} }

@ -11,17 +11,17 @@ pub struct BasicHashList {
pub hashes: Vec<String>, pub hashes: Vec<String>,
} }
#[derive(Clone, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
pub struct FileMetadataInfo { pub struct FileMetadataInfo {
pub file_id: u64, pub file_id: u64,
pub hash: String, pub hash: String,
pub size: u64, pub size: Option<u64>,
pub mime: String, pub mime: String,
pub ext: String, pub ext: String,
pub width: u32, pub width: Option<u32>,
pub height: u32, pub height: Option<u32>,
pub duration: Option<u64>, pub duration: Option<u64>,
pub has_audio: bool, pub has_audio: Option<bool>,
pub num_frames: Option<u16>, pub num_frames: Option<u16>,
pub num_words: Option<u64>, pub num_words: Option<u64>,
pub is_inbox: bool, pub is_inbox: bool,
@ -32,12 +32,18 @@ pub struct FileMetadataInfo {
pub service_names_to_statuses_to_display_tags: HashMap<String, HashMap<String, Vec<String>>>, pub service_names_to_statuses_to_display_tags: HashMap<String, HashMap<String, Vec<String>>>,
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub enum FileIdentifier { pub enum FileIdentifier {
ID(u64), ID(u64),
Hash(String), Hash(String),
} }
impl FileIdentifier {
pub fn hash<S: ToString>(hash: S) -> Self {
Self::Hash(hash.to_string())
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct FileRecord { pub struct FileRecord {
pub bytes: Vec<u8>, pub bytes: Vec<u8>,

@ -8,9 +8,9 @@ pub mod adding_urls;
pub mod common; pub mod common;
pub mod searching_and_fetching_files; pub mod searching_and_fetching_files;
pub trait Endpoint { pub(crate) trait Endpoint {
type Request: Serialize; type Request: Serialize;
type Response: DeserializeOwned; type Response: DeserializeOwned;
fn get_path() -> String; fn path() -> String;
} }

@ -44,14 +44,14 @@ impl Endpoint for SearchFiles {
type Request = (); type Request = ();
type Response = SearchFilesResponse; type Response = SearchFilesResponse;
fn get_path() -> String { fn path() -> String {
String::from("get_files/search_files") String::from("get_files/search_files")
} }
} }
#[derive(Clone, Default, Deserialize)] #[derive(Clone, Debug, Default, Deserialize)]
pub struct FileMetadataResponse { pub struct FileMetadataResponse {
metadata: Vec<FileMetadataInfo>, pub metadata: Vec<FileMetadataInfo>,
} }
pub struct FileMetadata; pub struct FileMetadata;
@ -60,7 +60,7 @@ impl Endpoint for FileMetadata {
type Request = (); type Request = ();
type Response = FileMetadataResponse; type Response = FileMetadataResponse;
fn get_path() -> String { fn path() -> String {
String::from("get_files/file_metadata") String::from("get_files/file_metadata")
} }
} }
@ -71,7 +71,7 @@ impl Endpoint for GetFile {
type Request = (); type Request = ();
type Response = (); type Response = ();
fn get_path() -> String { fn path() -> String {
String::from("get_files/file") String::from("get_files/file")
} }
} }

@ -1,3 +1,4 @@
use crate::endpoints::common::FileIdentifier;
use std::error::Error as StdError; use std::error::Error as StdError;
use std::fmt; use std::fmt;
@ -10,6 +11,7 @@ pub enum Error {
InvalidServiceType(String), InvalidServiceType(String),
ImportVetoed(String), ImportVetoed(String),
ImportFailed(String), ImportFailed(String),
FileNotFound(FileIdentifier),
} }
impl fmt::Display for Error { impl fmt::Display for Error {
@ -22,6 +24,7 @@ impl fmt::Display for Error {
} }
Self::ImportFailed(msg) => write!(f, "File import failed: {}", msg), Self::ImportFailed(msg) => write!(f, "File import failed: {}", msg),
Self::ImportVetoed(msg) => write!(f, "File import vetoed: {}", msg), Self::ImportVetoed(msg) => write!(f, "File import vetoed: {}", msg),
Self::FileNotFound(id) => write!(f, "File {:?} not found", id),
} }
} }
} }
@ -30,10 +33,7 @@ impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> { fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self { match self {
Self::Reqwest(e) => e.source(), Self::Reqwest(e) => e.source(),
Self::Hydrus(_) => None, _ => None,
Self::InvalidServiceType(_) => None,
Self::ImportVetoed(_) => None,
Self::ImportFailed(_) => None,
} }
} }
} }

@ -51,7 +51,7 @@ pub struct FileImportBuilder {
} }
impl FileImportBuilder { impl FileImportBuilder {
pub async fn run(mut self) -> Result<HydrusFile> { pub async fn run(self) -> Result<HydrusFile> {
let response = match self.file { let response = match self.file {
FileImport::Path(path) => self.client.add_file(path).await?, FileImport::Path(path) => self.client.add_file(path).await?,
FileImport::Binary(b) => self.client.add_binary_file(b).await?, FileImport::Binary(b) => self.client.add_binary_file(b).await?,
@ -137,7 +137,7 @@ impl UrlImportBuilder {
} }
/// Imports the URL /// Imports the URL
pub async fn run(mut self) -> Result<Url> { pub async fn run(self) -> Result<Url> {
let mut request = AddUrlRequestBuilder::default().url(&self.url); let mut request = AddUrlRequestBuilder::default().url(&self.url);
for (service, tags) in self.service_tag_mappings { for (service, tags) in self.service_tag_mappings {

@ -1,5 +1,7 @@
use crate::builders::import_builder::ImportBuilder; use crate::builders::import_builder::ImportBuilder;
use crate::endpoints::common::FileIdentifier;
use crate::error::Result; use crate::error::Result;
use crate::hydrus_file::HydrusFile;
use crate::models::url::Url; use crate::models::url::Url;
use crate::models::version::Version; use crate::models::version::Version;
use crate::service::Services; use crate::service::Services;
@ -16,7 +18,7 @@ impl Hydrus {
} }
/// Returns the Hydrus and API Version /// Returns the Hydrus and API Version
pub async fn version(&mut self) -> Result<Version> { pub async fn version(&self) -> Result<Version> {
let response = self.client.api_version().await?; let response = self.client.api_version().await?;
Ok(Version { Ok(Version {
api: response.version, api: response.version,
@ -25,14 +27,14 @@ impl Hydrus {
} }
/// Returns a list of available services /// Returns a list of available services
pub async fn services(&mut self) -> Result<Services> { pub async fn services(&self) -> Result<Services> {
let response = self.client.get_services().await?; let response = self.client.get_services().await?;
Ok(Services::from_response(self.client.clone(), response)) Ok(Services::from_response(self.client.clone(), response))
} }
/// Creates an import builder to build an import request to hydrus /// Creates an import builder to build an import request to hydrus
pub fn import(&mut self) -> ImportBuilder { pub fn import(&self) -> ImportBuilder {
ImportBuilder { ImportBuilder {
client: self.client.clone(), client: self.client.clone(),
} }
@ -40,7 +42,7 @@ impl Hydrus {
/// Returns information about a given url in an object that allows /// Returns information about a given url in an object that allows
/// further operations with that url /// further operations with that url
pub async fn url<S: AsRef<str>>(&mut self, url: S) -> Result<Url> { pub async fn url<S: AsRef<str>>(&self, url: S) -> Result<Url> {
let info = self.client.get_url_info(&url).await?; let info = self.client.get_url_info(&url).await?;
Ok(Url { Ok(Url {
@ -52,4 +54,14 @@ impl Hydrus {
can_parse: info.can_parse, can_parse: info.can_parse,
}) })
} }
/// Returns a file by identifier to perform further operations on
pub async fn file(&self, identifier: FileIdentifier) -> Result<HydrusFile> {
let metadata = self
.client
.get_file_metadata_by_identifier(identifier)
.await?;
Ok(HydrusFile::from_metadata(self.client.clone(), metadata))
}
} }

@ -1,5 +1,9 @@
use crate::endpoints::common::FileIdentifier; use crate::endpoints::common::{FileIdentifier, FileMetadataInfo};
use crate::error::Result;
use crate::service::ServiceName;
use crate::tag::Tag;
use crate::Client; use crate::Client;
use std::collections::HashMap;
#[derive(Clone, Debug, PartialOrd, PartialEq)] #[derive(Clone, Debug, PartialOrd, PartialEq)]
pub enum FileStatus { pub enum FileStatus {
@ -16,6 +20,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>,
} }
impl HydrusFile { impl HydrusFile {
@ -35,6 +40,101 @@ impl HydrusFile {
client, client,
id: FileIdentifier::Hash(hash.to_string()), id: FileIdentifier::Hash(hash.to_string()),
status, status,
metadata: None,
} }
} }
pub(crate) fn from_metadata(client: Client, metadata: FileMetadataInfo) -> Self {
let status = if metadata.is_trashed {
FileStatus::Deleted
} else {
FileStatus::InDatabase
};
Self {
client,
id: FileIdentifier::Hash(metadata.hash.clone()),
status,
metadata: Some(metadata),
}
}
/// Deletes the internally stored metadata about the file retrieves it again
pub async fn update(&mut self) -> Result<()> {
self.metadata = None;
self.metadata().await?;
Ok(())
}
/// 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> {
if self.metadata.is_none() {
let metadata = self
.client
.get_file_metadata_by_identifier(self.id.clone())
.await?;
self.status = if metadata.is_trashed {
FileStatus::Deleted
} else {
FileStatus::InDatabase
};
self.metadata = Some(metadata);
}
Ok(self.metadata.as_ref().unwrap())
}
/// Returns the hash of the file
/// if the file identifier is an id it calls hydrus to resolve the file
pub async fn hash(&mut self) -> Result<String> {
match &self.id {
FileIdentifier::ID(_) => {
let metadata = self.metadata().await?;
Ok(metadata.hash.clone())
}
FileIdentifier::Hash(hash) => Ok(hash.clone()),
}
}
/// Associates the file with a list of urls
pub async fn associate_urls(&mut self, urls: Vec<String>) -> Result<()> {
let hash = self.hash().await?;
self.client.associate_urls(urls, vec![hash]).await
}
/// Disassociates the file with a list of urls
pub async fn disassociate_urls(&mut self, urls: Vec<String>) -> Result<()> {
let hash = self.hash().await?;
self.client.disassociate_urls(urls, vec![hash]).await
}
/// Returns map mapping lists of tags to services
pub async fn services_with_tags(&mut self) -> Result<HashMap<ServiceName, Vec<Tag>>> {
let metadata = self.metadata().await?;
let mut tag_mappings = HashMap::new();
for (service, status_tags) in &metadata.service_names_to_statuses_to_tags {
let mut tag_list = Vec::new();
for (_, tags) in status_tags {
tag_list.append(&mut tags.into_iter().map(|t| t.into()).collect())
}
tag_mappings.insert(ServiceName(service.clone()), tag_list);
}
Ok(tag_mappings)
}
/// Returns a list of all tags assigned to the file
pub async fn tags(&mut self) -> Result<Vec<Tag>> {
let mut tag_list = Vec::new();
let tag_mappings = self.services_with_tags().await?;
for (_, mut tags) in tag_mappings {
tag_list.append(&mut tags);
}
Ok(tag_list)
}
} }

@ -56,9 +56,11 @@ impl ToString for ServiceType {
} }
} }
#[derive(Clone)] #[derive(Clone, PartialOrd, PartialEq, Hash)]
pub struct ServiceName(pub String); pub struct ServiceName(pub String);
impl Eq for ServiceName {}
impl ServiceName { impl ServiceName {
pub fn my_tags() -> Self { pub fn my_tags() -> Self {
Self(String::from("my tags")) Self(String::from("my tags"))

@ -2,7 +2,7 @@ use super::super::common;
#[tokio::test] #[tokio::test]
async fn it_returns_the_api_version() { async fn it_returns_the_api_version() {
let mut client = common::get_client(); let client = common::get_client();
let api_version = client.api_version().await.unwrap(); let api_version = client.api_version().await.unwrap();
assert!(api_version.hydrus_version > 0); assert!(api_version.hydrus_version > 0);
assert!(api_version.version > 0); assert!(api_version.version > 0);
@ -10,14 +10,14 @@ async fn it_returns_the_api_version() {
#[tokio::test] #[tokio::test]
async fn it_returns_the_session_key() { async fn it_returns_the_session_key() {
let mut client = common::get_client(); let client = common::get_client();
let session_key = client.session_key().await.unwrap(); let session_key = client.session_key().await.unwrap();
assert!(session_key.session_key.len() > 0); assert!(session_key.session_key.len() > 0);
} }
#[tokio::test] #[tokio::test]
async fn it_verifies_the_access_key() { async fn it_verifies_the_access_key() {
let mut client = common::get_client(); let client = common::get_client();
let verification_response = client.verify_access_key().await.unwrap(); let verification_response = client.verify_access_key().await.unwrap();
assert!(verification_response.basic_permissions.len() > 0); // needs to be configured in the client but we want at least some permissions for the test assert!(verification_response.basic_permissions.len() > 0); // needs to be configured in the client but we want at least some permissions for the test
assert!(verification_response.human_description.len() > 0); assert!(verification_response.human_description.len() > 0);
@ -25,7 +25,7 @@ async fn it_verifies_the_access_key() {
#[tokio::test] #[tokio::test]
async fn it_returns_a_list_of_services() { async fn it_returns_a_list_of_services() {
let mut client = common::get_client(); let client = common::get_client();
let services_response = client.get_services().await.unwrap(); let services_response = client.get_services().await.unwrap();
assert!(services_response.0.keys().len() > 0); assert!(services_response.0.keys().len() > 0);
} }

@ -2,14 +2,14 @@ use super::super::common;
#[tokio::test] #[tokio::test]
async fn it_adds_files() { async fn it_adds_files() {
let mut client = common::get_client(); let client = common::get_client();
let result = client.add_file("/does/not/exist").await; let result = client.add_file("/does/not/exist").await;
assert!(result.is_err()); // because the path does not exist assert!(result.is_err()); // because the path does not exist
} }
#[tokio::test] #[tokio::test]
async fn it_adds_binary_files() { async fn it_adds_binary_files() {
let mut client = common::get_client(); let client = common::get_client();
let result = client let result = client
.add_binary_file(vec![0u8, 0u8, 0u8, 0u8]) .add_binary_file(vec![0u8, 0u8, 0u8, 0u8])
.await .await
@ -19,24 +19,24 @@ async fn it_adds_binary_files() {
#[tokio::test] #[tokio::test]
async fn it_deletes_files() { async fn it_deletes_files() {
let mut client = common::get_client(); let client = common::get_client();
client.delete_files(vec![]).await.unwrap(); client.delete_files(vec![]).await.unwrap();
} }
#[tokio::test] #[tokio::test]
async fn it_undeletes_files() { async fn it_undeletes_files() {
let mut client = common::get_client(); let client = common::get_client();
client.undelete_files(vec![]).await.unwrap(); client.undelete_files(vec![]).await.unwrap();
} }
#[tokio::test] #[tokio::test]
async fn it_archives_files() { async fn it_archives_files() {
let mut client = common::get_client(); let client = common::get_client();
client.archive_files(vec![]).await.unwrap(); client.archive_files(vec![]).await.unwrap();
} }
#[tokio::test] #[tokio::test]
async fn it_unarchives_files() { async fn it_unarchives_files() {
let mut client = common::get_client(); let client = common::get_client();
client.unarchive_files(vec![]).await.unwrap(); client.unarchive_files(vec![]).await.unwrap();
} }

@ -3,7 +3,7 @@ use hydrus_api::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction};
#[tokio::test] #[tokio::test]
async fn it_cleans_tags() { async fn it_cleans_tags() {
let mut client = common::get_client(); let client = common::get_client();
let response = client let response = client
.clean_tags(vec![ .clean_tags(vec![
"summer".into(), "summer".into(),
@ -18,7 +18,7 @@ async fn it_cleans_tags() {
#[tokio::test] #[tokio::test]
async fn it_adds_tags() { async fn it_adds_tags() {
let mut client = common::get_client(); let client = common::get_client();
let request = AddTagsRequestBuilder::default() let request = AddTagsRequestBuilder::default()
.add_hash("0000000000000000000000000000000000000000000000000000000000000000") // valid hash, I hope no files are affected .add_hash("0000000000000000000000000000000000000000000000000000000000000000") // valid hash, I hope no files are affected
.add_tags("my tags", vec!["beach".into(), "summer".into()]) .add_tags("my tags", vec!["beach".into(), "summer".into()])

@ -3,7 +3,7 @@ use hydrus_api::endpoints::adding_urls::{AddUrlRequestBuilder, URL_TYPE_POST};
#[tokio::test] #[tokio::test]
async fn it_returns_files_for_an_url() { async fn it_returns_files_for_an_url() {
let mut client = common::get_client(); let client = common::get_client();
let response = client let response = client
.get_url_files("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .get_url_files("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium")
.await .await
@ -14,7 +14,7 @@ async fn it_returns_files_for_an_url() {
#[tokio::test] #[tokio::test]
async fn it_returns_url_information() { async fn it_returns_url_information() {
let mut client = common::get_client(); let client = common::get_client();
let info = client let info = client
.get_url_info("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .get_url_info("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium")
.await .await
@ -25,7 +25,7 @@ async fn it_returns_url_information() {
#[tokio::test] #[tokio::test]
async fn it_adds_urls() { async fn it_adds_urls() {
let mut client = common::get_client(); let client = common::get_client();
let request = AddUrlRequestBuilder::default() let request = AddUrlRequestBuilder::default()
.url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium")
.add_tags( .add_tags(
@ -41,7 +41,7 @@ async fn it_adds_urls() {
#[tokio::test] #[tokio::test]
async fn it_associates_urls() { async fn it_associates_urls() {
let mut client = common::get_client(); let client = common::get_client();
client client
.associate_urls( .associate_urls(
vec![ vec![
@ -56,7 +56,7 @@ async fn it_associates_urls() {
#[tokio::test] #[tokio::test]
async fn it_disassociates_urls() { async fn it_disassociates_urls() {
let mut client = common::get_client(); let client = common::get_client();
client client
.disassociate_urls( .disassociate_urls(
vec![ vec![

@ -4,7 +4,7 @@ use hydrus_api::endpoints::searching_and_fetching_files::FileSearchLocation;
#[tokio::test] #[tokio::test]
async fn is_searches_files() { async fn is_searches_files() {
let mut client = common::get_client(); let client = common::get_client();
client client
.search_files(vec!["beach".to_string()], FileSearchLocation::Archive) .search_files(vec!["beach".to_string()], FileSearchLocation::Archive)
.await .await
@ -13,19 +13,19 @@ async fn is_searches_files() {
#[tokio::test] #[tokio::test]
async fn it_fetches_file_metadata() { async fn it_fetches_file_metadata() {
let mut client = common::get_client(); let client = common::get_client();
client let response = client
.get_file_metadata( .get_file_metadata(
vec![], vec![],
vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()], vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()],
) )
.await .await;
.unwrap(); assert!(response.is_ok()); // Even if the file doesn't exist it still returns some information about it
} }
#[tokio::test] #[tokio::test]
async fn it_fetches_single_files() { async fn it_fetches_single_files() {
let mut client = common::get_client(); let client = common::get_client();
let response = client let response = client
.get_file(FileIdentifier::Hash( .get_file(FileIdentifier::Hash(
"0000000000000000000000000000000000000000000000000000000000000000".to_string(), "0000000000000000000000000000000000000000000000000000000000000000".to_string(),

@ -1,3 +1,4 @@
mod test_files;
mod test_hydrus; mod test_hydrus;
mod test_import; mod test_import;
mod test_url; mod test_url;

@ -0,0 +1,49 @@
use super::super::common;
use hydrus_api::endpoints::common::FileIdentifier;
use hydrus_api::hydrus_file::HydrusFile;
async fn get_file() -> HydrusFile {
let hydrus = common::get_hydrus();
hydrus
.file(FileIdentifier::hash(
"277a138cd1ee79fc1fdb2869c321b848d4861e45b82184487139ef66dd40b62d", // needs to exist
))
.await
.unwrap()
}
#[tokio::test]
async fn it_associates_with_urls() {
let mut file = get_file().await;
file.associate_urls(vec![
"https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium".to_string(),
])
.await
.unwrap();
}
#[tokio::test]
async fn it_disassociates_with_urls() {
let mut file = get_file().await;
file.disassociate_urls(vec![
"https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium".to_string(),
])
.await
.unwrap();
}
#[tokio::test]
async fn it_has_tags_with_services() {
let mut file = get_file().await;
let tags = file.services_with_tags().await.unwrap();
assert!(tags.keys().len() > 0)
}
#[tokio::test]
async fn it_has_tags() {
let mut file = get_file().await;
let tags = file.tags().await.unwrap();
assert!(tags.len() > 0) // test data needs to be prepared this way
}

@ -4,7 +4,7 @@ use hydrus_api::url::UrlType;
#[tokio::test] #[tokio::test]
async fn it_retrieves_version_info() { async fn it_retrieves_version_info() {
let mut hydrus = common::get_hydrus(); let hydrus = common::get_hydrus();
let version = hydrus.version().await.unwrap(); let version = hydrus.version().await.unwrap();
assert!(version.hydrus > 0); assert!(version.hydrus > 0);
assert!(version.api > 0); assert!(version.api > 0);
@ -12,7 +12,7 @@ async fn it_retrieves_version_info() {
#[tokio::test] #[tokio::test]
async fn it_retrieves_services() { async fn it_retrieves_services() {
let mut hydrus = common::get_hydrus(); let hydrus = common::get_hydrus();
let services = hydrus.services().await.unwrap(); let services = hydrus.services().await.unwrap();
// assuming hydrus is configured correctly // assuming hydrus is configured correctly
@ -22,7 +22,7 @@ async fn it_retrieves_services() {
#[tokio::test] #[tokio::test]
async fn it_retrieves_url_information() { async fn it_retrieves_url_information() {
let mut hydrus = common::get_hydrus(); let hydrus = common::get_hydrus();
let url = hydrus let url = hydrus
.url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium")
.await .await

@ -7,7 +7,7 @@ use hydrus_api::url::UrlType;
#[tokio::test] #[tokio::test]
async fn it_imports_file_paths() { async fn it_imports_file_paths() {
let mut hydrus = common::get_hydrus(); let hydrus = common::get_hydrus();
let result = hydrus let result = hydrus
.import() .import()
.file(FileImport::path("/does/not/exist/sadly")) .file(FileImport::path("/does/not/exist/sadly"))
@ -19,7 +19,7 @@ async fn it_imports_file_paths() {
#[tokio::test] #[tokio::test]
async fn it_imports_binary_files() { async fn it_imports_binary_files() {
let mut hydrus = common::get_hydrus(); let hydrus = common::get_hydrus();
let bytes = [0u8, 0u8, 0u8, 0u8]; let bytes = [0u8, 0u8, 0u8, 0u8];
let result = hydrus let result = hydrus
.import() .import()
@ -32,7 +32,7 @@ async fn it_imports_binary_files() {
#[tokio::test] #[tokio::test]
async fn it_imports_urls() { async fn it_imports_urls() {
let mut hydrus = common::get_hydrus(); let hydrus = common::get_hydrus();
let result = hydrus let result = hydrus
.import() .import()

@ -5,7 +5,7 @@ use hydrus_api::tag::Tag;
use hydrus_api::url::Url; use hydrus_api::url::Url;
async fn get_url() -> Url { async fn get_url() -> Url {
let mut hydrus = common::get_hydrus(); let hydrus = common::get_hydrus();
hydrus hydrus
.url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium")
.await .await

Loading…
Cancel
Save