From 21cd993416af43884a63552535890ea114607e36 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 10 Jul 2021 17:45:08 +0200 Subject: [PATCH 01/12] Increment version Signed-off-by: trivernis --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 23cd4fc..2879b15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydrus-api" -version = "0.1.0" +version = "0.2.0" authors = ["trivernis "] edition = "2018" license = "Apache-2.0" From ef8d998efb74cbdd47401d02a66949f82e2469e5 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 11 Jul 2021 12:20:12 +0200 Subject: [PATCH 02/12] Add hydrus wrapper with services and version info Signed-off-by: trivernis --- src/client.rs | 1 + src/endpoints/access_management.rs | 9 ++ src/error.rs | 5 + src/lib.rs | 3 + src/models/builders/mod.rs | 0 src/models/hydrus.rs | 31 +++++ src/models/mod.rs | 4 + src/models/service.rs | 113 ++++++++++++++++++ src/models/version.rs | 4 + tests/client/mod.rs | 5 + tests/{ => client}/test_access_management.rs | 2 +- tests/{ => client}/test_adding_files.rs | 2 +- tests/{ => client}/test_adding_tags.rs | 2 +- tests/{ => client}/test_adding_urls.rs | 3 +- .../test_searching_and_fetching_files.rs | 3 +- tests/common.rs | 8 ++ tests/mod.rs | 3 + tests/wrapper/mod.rs | 1 + tests/wrapper/test_hydrus.rs | 20 ++++ 19 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 src/models/builders/mod.rs create mode 100644 src/models/hydrus.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/service.rs create mode 100644 src/models/version.rs create mode 100644 tests/client/mod.rs rename tests/{ => client}/test_access_management.rs (97%) rename tests/{ => client}/test_adding_files.rs (97%) rename tests/{ => client}/test_adding_tags.rs (97%) rename tests/{ => client}/test_adding_urls.rs (98%) rename tests/{ => client}/test_searching_and_fetching_files.rs (97%) create mode 100644 tests/mod.rs create mode 100644 tests/wrapper/mod.rs create mode 100644 tests/wrapper/test_hydrus.rs diff --git a/src/client.rs b/src/client.rs index 1b2eddb..fc162ab 100644 --- a/src/client.rs +++ b/src/client.rs @@ -25,6 +25,7 @@ use serde::Serialize; static ACCESS_KEY_HEADER: &str = "Hydrus-Client-API-Access-Key"; +#[derive(Clone)] pub struct Client { inner: reqwest::Client, base_url: String, diff --git a/src/endpoints/access_management.rs b/src/endpoints/access_management.rs index 96d759c..6f2597d 100644 --- a/src/endpoints/access_management.rs +++ b/src/endpoints/access_management.rs @@ -2,6 +2,15 @@ use crate::endpoints::common::BasicServiceInfo; use crate::endpoints::Endpoint; use std::collections::HashMap; +pub static SERVICE_TYPE_LOCAL_TAGS: &str = "local_tags"; +pub static SERVICE_TYPE_TAG_REPOSITORIES: &str = "tag_repositories"; +pub static SERVICE_TYPE_LOCAL_FILES: &str = "local_files"; +pub static SERVICE_TYPE_FILE_REPOSITORIES: &str = "file_repositories"; +pub static SERVICE_TYPE_ALL_LOCAL_FILES: &str = "all_local_files"; +pub static SERVICE_TYPE_ALL_KNOWN_FILES: &str = "all_known_files"; +pub static SERVICE_TYPE_ALL_KNOWN_TAGS: &str = "all_known_tags"; +pub static SERVICE_TYPE_TRASH: &str = "trash"; + #[derive(Debug, Clone, Deserialize)] pub struct ApiVersionResponse { pub version: u32, diff --git a/src/error.rs b/src/error.rs index 34e7d64..a0cb93f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,6 +7,7 @@ pub type Result = std::result::Result; pub enum Error { Reqwest(reqwest::Error), Hydrus(String), + InvalidServiceType(String), } impl fmt::Display for Error { @@ -14,6 +15,9 @@ impl fmt::Display for Error { match self { Self::Reqwest(e) => e.fmt(f), Self::Hydrus(msg) => msg.fmt(f), + Self::InvalidServiceType(service_type) => { + write!(f, "Invalid Service Type '{}'", service_type) + } } } } @@ -23,6 +27,7 @@ impl StdError for Error { match self { Self::Reqwest(e) => e.source(), Self::Hydrus(_) => None, + Self::InvalidServiceType(_) => None, } } } diff --git a/src/lib.rs b/src/lib.rs index 068f010..956c588 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,9 @@ extern crate serde; pub mod client; pub mod endpoints; pub mod error; +mod models; pub(crate) mod utils; pub use client::Client; +pub use models::hydrus::Hydrus; +pub use models::*; diff --git a/src/models/builders/mod.rs b/src/models/builders/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/models/hydrus.rs b/src/models/hydrus.rs new file mode 100644 index 0000000..19137ee --- /dev/null +++ b/src/models/hydrus.rs @@ -0,0 +1,31 @@ +use crate::error::Result; +use crate::models::version::Version; +use crate::service::Services; +use crate::Client; + +pub struct Hydrus { + client: Client, +} + +impl Hydrus { + /// Creates a new high level Hydrus API client + pub fn new(client: Client) -> Self { + Self { client } + } + + /// Returns the Hydrus and API Version + pub async fn version(&mut self) -> Result { + let response = self.client.api_version().await?; + Ok(Version { + api: response.version, + hydrus: response.hydrus_version, + }) + } + + /// Returns a list of available services + pub async fn services(&mut self) -> Result { + let response = self.client.get_services().await?; + + Ok(Services::from_response(self.client.clone(), response)) + } +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..7a1c167 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,4 @@ +pub mod builders; +pub mod hydrus; +pub mod service; +pub mod version; diff --git a/src/models/service.rs b/src/models/service.rs new file mode 100644 index 0000000..a0f36d3 --- /dev/null +++ b/src/models/service.rs @@ -0,0 +1,113 @@ +use crate::endpoints::access_management::GetServicesResponse; +use crate::endpoints::access_management::{ + SERVICE_TYPE_ALL_KNOWN_FILES, SERVICE_TYPE_ALL_KNOWN_TAGS, SERVICE_TYPE_ALL_LOCAL_FILES, + SERVICE_TYPE_FILE_REPOSITORIES, SERVICE_TYPE_LOCAL_FILES, SERVICE_TYPE_LOCAL_TAGS, + SERVICE_TYPE_TAG_REPOSITORIES, SERVICE_TYPE_TRASH, +}; +use crate::error::Error; +use crate::Client; +use std::collections::HashMap; +use std::convert::TryFrom; + +#[derive(Clone, PartialOrd, PartialEq, Hash)] +pub enum ServiceType { + LocalTags, + TagRepositories, + LocalFiles, + FileRepositories, + AllLocalFiles, + AllKnownFiles, + AllKnownTags, + Trash, +} + +impl Eq for ServiceType {} + +impl TryFrom for ServiceType { + type Error = Error; + + fn try_from(value: String) -> Result { + match value.as_str() { + s if s == SERVICE_TYPE_LOCAL_TAGS => Ok(Self::LocalTags), + s if s == SERVICE_TYPE_TAG_REPOSITORIES => Ok(Self::TagRepositories), + s if s == SERVICE_TYPE_LOCAL_FILES => Ok(Self::LocalFiles), + s if s == SERVICE_TYPE_FILE_REPOSITORIES => Ok(Self::FileRepositories), + s if s == SERVICE_TYPE_ALL_LOCAL_FILES => Ok(Self::AllLocalFiles), + s if s == SERVICE_TYPE_ALL_KNOWN_FILES => Ok(Self::AllKnownFiles), + s if s == SERVICE_TYPE_ALL_KNOWN_TAGS => Ok(Self::AllKnownTags), + s if s == SERVICE_TYPE_TRASH => Ok(Self::Trash), + _ => Err(Error::InvalidServiceType(value)), + } + } +} + +impl ToString for ServiceType { + fn to_string(&self) -> String { + match self { + ServiceType::LocalTags => String::from(SERVICE_TYPE_LOCAL_TAGS), + ServiceType::TagRepositories => String::from(SERVICE_TYPE_TAG_REPOSITORIES), + ServiceType::LocalFiles => String::from(SERVICE_TYPE_LOCAL_FILES), + ServiceType::FileRepositories => String::from(SERVICE_TYPE_FILE_REPOSITORIES), + ServiceType::AllLocalFiles => String::from(SERVICE_TYPE_ALL_LOCAL_FILES), + ServiceType::AllKnownFiles => String::from(SERVICE_TYPE_ALL_KNOWN_FILES), + ServiceType::AllKnownTags => String::from(SERVICE_TYPE_ALL_KNOWN_TAGS), + ServiceType::Trash => String::from(SERVICE_TYPE_TRASH), + } + } +} + +#[derive(Clone)] +pub struct Service { + client: Client, + pub name: String, + pub key: String, + pub service_type: ServiceType, +} + +#[derive(Clone)] +pub struct Services { + inner: HashMap>, +} + +impl Services { + pub fn from_response(client: Client, response: GetServicesResponse) -> Self { + let mut response = response.0; + let mut mapped_types = HashMap::with_capacity(response.keys().len()); + let keys = response.keys().cloned().collect::>().clone(); + + for service_type in &keys { + if let Ok(mapped_type) = ServiceType::try_from(service_type.clone()) { + let basic_services = response.remove(service_type).unwrap(); + let mut service_list = Vec::new(); + + for basic_service in basic_services { + service_list.push(Service { + service_type: mapped_type.clone(), + name: basic_service.name, + key: basic_service.service_key, + client: client.clone(), + }) + } + + mapped_types.insert(mapped_type, service_list); + } + } + + Self { + inner: mapped_types, + } + } + + /// Returns a list of all services of the given type + pub fn get_services(&self, service_type: ServiceType) -> Vec<&Service> { + if let Some(services) = self.inner.get(&service_type) { + let mut borrowed_services = Vec::with_capacity(services.len()); + for service in services { + borrowed_services.push(service) + } + borrowed_services + } else { + Vec::with_capacity(0) + } + } +} diff --git a/src/models/version.rs b/src/models/version.rs new file mode 100644 index 0000000..5150d26 --- /dev/null +++ b/src/models/version.rs @@ -0,0 +1,4 @@ +pub struct Version { + pub api: u32, + pub hydrus: u32, +} diff --git a/tests/client/mod.rs b/tests/client/mod.rs new file mode 100644 index 0000000..3c81da6 --- /dev/null +++ b/tests/client/mod.rs @@ -0,0 +1,5 @@ +mod test_access_management; +mod test_adding_files; +mod test_adding_tags; +mod test_adding_urls; +mod test_searching_and_fetching_files; diff --git a/tests/test_access_management.rs b/tests/client/test_access_management.rs similarity index 97% rename from tests/test_access_management.rs rename to tests/client/test_access_management.rs index a06313f..1834fca 100644 --- a/tests/test_access_management.rs +++ b/tests/client/test_access_management.rs @@ -1,4 +1,4 @@ -mod common; +use super::super::common; #[tokio::test] async fn it_returns_the_api_version() { diff --git a/tests/test_adding_files.rs b/tests/client/test_adding_files.rs similarity index 97% rename from tests/test_adding_files.rs rename to tests/client/test_adding_files.rs index 5c4bc2d..aa12bcc 100644 --- a/tests/test_adding_files.rs +++ b/tests/client/test_adding_files.rs @@ -1,4 +1,4 @@ -mod common; +use super::super::common; #[tokio::test] async fn it_adds_files() { diff --git a/tests/test_adding_tags.rs b/tests/client/test_adding_tags.rs similarity index 97% rename from tests/test_adding_tags.rs rename to tests/client/test_adding_tags.rs index eb98b11..819cdc1 100644 --- a/tests/test_adding_tags.rs +++ b/tests/client/test_adding_tags.rs @@ -1,5 +1,5 @@ +use super::super::common; use hydrus_api::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; -mod common; #[tokio::test] async fn it_cleans_tags() { diff --git a/tests/test_adding_urls.rs b/tests/client/test_adding_urls.rs similarity index 98% rename from tests/test_adding_urls.rs rename to tests/client/test_adding_urls.rs index f8672ef..01e5af0 100644 --- a/tests/test_adding_urls.rs +++ b/tests/client/test_adding_urls.rs @@ -1,7 +1,6 @@ +use super::super::common; use hydrus_api::endpoints::adding_urls::{AddUrlRequestBuilder, URL_TYPE_POST}; -mod common; - #[tokio::test] async fn it_returns_files_for_an_url() { let mut client = common::get_client(); diff --git a/tests/test_searching_and_fetching_files.rs b/tests/client/test_searching_and_fetching_files.rs similarity index 97% rename from tests/test_searching_and_fetching_files.rs rename to tests/client/test_searching_and_fetching_files.rs index 3403579..42d4940 100644 --- a/tests/test_searching_and_fetching_files.rs +++ b/tests/client/test_searching_and_fetching_files.rs @@ -1,8 +1,7 @@ +use super::super::common; 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(); diff --git a/tests/common.rs b/tests/common.rs index d84f128..48d41c4 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,4 +1,5 @@ use hydrus_api::client::Client; +use hydrus_api::Hydrus; use log::LevelFilter; use std::env; use std::sync::atomic::{AtomicBool, Ordering}; @@ -15,9 +16,16 @@ pub fn setup() { pub fn get_client() -> Client { setup(); + Client::new( env::var("HYDRUS_URL").unwrap(), env::var("HYDRUS_ACCESS_KEY").unwrap(), ) .unwrap() } + +pub fn get_hydrus() -> Hydrus { + let client = get_client(); + + Hydrus::new(client) +} diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 0000000..aad7eab --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1,3 @@ +mod client; +mod common; +mod wrapper; diff --git a/tests/wrapper/mod.rs b/tests/wrapper/mod.rs new file mode 100644 index 0000000..bcb74a6 --- /dev/null +++ b/tests/wrapper/mod.rs @@ -0,0 +1 @@ +mod test_hydrus; diff --git a/tests/wrapper/test_hydrus.rs b/tests/wrapper/test_hydrus.rs new file mode 100644 index 0000000..4f46687 --- /dev/null +++ b/tests/wrapper/test_hydrus.rs @@ -0,0 +1,20 @@ +use super::super::common; +use hydrus_api::service::ServiceType; + +#[tokio::test] +async fn it_retrieves_version_info() { + let mut hydrus = common::get_hydrus(); + let version = hydrus.version().await.unwrap(); + assert!(version.hydrus > 0); + assert!(version.api > 0); +} + +#[tokio::test] +async fn it_retrieves_services() { + let mut hydrus = common::get_hydrus(); + let services = hydrus.services().await.unwrap(); + + // assuming hydrus is configured correctly + assert!(services.get_services(ServiceType::AllKnownFiles).len() > 0); + assert!(services.get_services(ServiceType::AllKnownTags).len() > 0); +} From 798e37141ec2d8f4af44d873f018e11da768ee8c Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 11 Jul 2021 13:39:18 +0200 Subject: [PATCH 03/12] Add importing to hydrus wrapper Signed-off-by: trivernis --- src/endpoints/adding_files.rs | 6 + src/endpoints/common.rs | 2 + src/error.rs | 6 + src/models/builders/import_builder.rs | 158 ++++++++++++++++++++++++++ src/models/builders/mod.rs | 1 + src/models/hydrus.rs | 8 ++ src/models/hydrus_file.rs | 8 ++ src/models/mod.rs | 4 + src/models/page.rs | 20 ++++ src/models/service.rs | 34 +++++- src/models/tag.rs | 35 ++++++ src/models/url.rs | 5 + src/utils.rs | 7 ++ tests/wrapper/mod.rs | 1 + tests/wrapper/test_import.rs | 48 ++++++++ 15 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 src/models/builders/import_builder.rs create mode 100644 src/models/hydrus_file.rs create mode 100644 src/models/page.rs create mode 100644 src/models/tag.rs create mode 100644 src/models/url.rs create mode 100644 tests/wrapper/test_import.rs diff --git a/src/endpoints/adding_files.rs b/src/endpoints/adding_files.rs index 2ff9892..09da721 100644 --- a/src/endpoints/adding_files.rs +++ b/src/endpoints/adding_files.rs @@ -1,6 +1,12 @@ use crate::endpoints::common::BasicHashList; use crate::endpoints::Endpoint; +pub static STATUS_IMPORT_SUCCESS: u8 = 1; +pub static STATUS_IMPORT_ALREADY_EXISTS: u8 = 2; +pub static STATUS_IMPORT_PREVIOUSLY_DELETED: u8 = 3; +pub static STATUS_IMPORT_FAILED: u8 = 4; +pub static STATUS_IMPORT_VETOED: u8 = 5; + #[derive(Debug, Clone, Serialize)] pub struct AddFileRequest { pub path: String, diff --git a/src/endpoints/common.rs b/src/endpoints/common.rs index b4e5862..f3d368d 100644 --- a/src/endpoints/common.rs +++ b/src/endpoints/common.rs @@ -32,11 +32,13 @@ pub struct FileMetadataInfo { pub service_names_to_statuses_to_display_tags: HashMap>>, } +#[derive(Clone)] pub enum FileIdentifier { ID(u64), Hash(String), } +#[derive(Clone)] pub struct FileRecord { pub bytes: Vec, pub mime_type: String, diff --git a/src/error.rs b/src/error.rs index a0cb93f..24010d0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,8 @@ pub enum Error { Reqwest(reqwest::Error), Hydrus(String), InvalidServiceType(String), + ImportVetoed(String), + ImportFailed(String), } impl fmt::Display for Error { @@ -18,6 +20,8 @@ impl fmt::Display for Error { Self::InvalidServiceType(service_type) => { write!(f, "Invalid Service Type '{}'", service_type) } + Self::ImportFailed(msg) => write!(f, "File import failed: {}", msg), + Self::ImportVetoed(msg) => write!(f, "File import vetoed: {}", msg), } } } @@ -28,6 +32,8 @@ impl StdError for Error { Self::Reqwest(e) => e.source(), Self::Hydrus(_) => None, Self::InvalidServiceType(_) => None, + Self::ImportVetoed(_) => None, + Self::ImportFailed(_) => None, } } } diff --git a/src/models/builders/import_builder.rs b/src/models/builders/import_builder.rs new file mode 100644 index 0000000..dbf8ff6 --- /dev/null +++ b/src/models/builders/import_builder.rs @@ -0,0 +1,158 @@ +use crate::endpoints::adding_files::{STATUS_IMPORT_FAILED, STATUS_IMPORT_VETOED}; +use crate::endpoints::adding_urls::AddUrlRequestBuilder; +use crate::endpoints::common::FileIdentifier; +use crate::error::{Error, Result}; +use crate::hydrus_file::HydrusFile; +use crate::models::url::Url; +use crate::page::PageIdentifier; +use crate::service::ServiceName; +use crate::tag::Tag; +use crate::utils::tag_list_to_string_list; +use crate::Client; +use std::collections::HashMap; +use std::io::Read; + +pub struct ImportBuilder { + pub(crate) client: Client, +} + +impl ImportBuilder { + pub fn file(self, file: FileImport) -> FileImportBuilder { + FileImportBuilder { + client: self.client, + file, + } + } + + pub fn url(self, url: S) -> UrlImportBuilder { + UrlImportBuilder { + client: self.client, + url: url.to_string(), + page: None, + show_page: false, + filter_tags: vec![], + service_tag_mappings: Default::default(), + } + } +} + +pub enum FileImport { + Path(String), + Binary(Vec), +} + +impl FileImport { + pub fn path(path: S) -> Self { + Self::Path(path.to_string()) + } + + pub fn binary(reader: &mut R) -> Self { + let mut bytes = Vec::new(); + let _ = reader.read_to_end(&mut bytes); + Self::Binary(bytes) + } +} + +pub struct FileImportBuilder { + client: Client, + file: FileImport, +} + +pub struct UrlImportBuilder { + client: Client, + url: String, + page: Option, + show_page: bool, + filter_tags: Vec, + service_tag_mappings: HashMap>, +} + +impl FileImportBuilder { + pub async fn run(mut self) -> Result { + let response = match self.file { + FileImport::Path(path) => self.client.add_file(path).await?, + FileImport::Binary(b) => self.client.add_binary_file(b).await?, + }; + + if response.status == STATUS_IMPORT_FAILED { + Err(Error::ImportFailed(response.note)) + } else if response.status == STATUS_IMPORT_VETOED { + Err(Error::ImportVetoed(response.note)) + } else { + Ok(HydrusFile { + client: self.client, + id: FileIdentifier::Hash(response.hash), + }) + } + } +} + +impl UrlImportBuilder { + /// Sets the destination page of the import + pub fn set_page(mut self, page: PageIdentifier) -> Self { + self.page = Some(page); + + self + } + + /// If the destination page of the import should be focussed + pub fn show_page(mut self, show: bool) -> Self { + self.show_page = show; + + self + } + + /// Adds a tag that should be filtered + pub fn add_filter_tag(mut self, tag: Tag) -> Self { + self.filter_tags.push(tag); + + self + } + + /// Adds multiple tags that should be filtered + pub fn add_filter_tags(mut self, mut tags: Vec) -> Self { + self.filter_tags.append(&mut tags); + + self + } + + /// Adds an additional tag for the imported file + pub fn add_additional_tag(self, service: ServiceName, tag: Tag) -> Self { + self.add_additional_tags(service, vec![tag]) + } + + /// Adds multiple additional tags for the import + pub fn add_additional_tags(mut self, service: ServiceName, mut tags: Vec) -> Self { + if let Some(service_tags) = self.service_tag_mappings.get_mut(&service.0) { + service_tags.append(&mut tags); + } else { + self.service_tag_mappings.insert(service.0, tags); + } + + self + } + + /// Imports the URL + pub async fn run(mut self) -> Result { + let mut request = AddUrlRequestBuilder::default().url(self.url.clone()); + + for (service, tags) in self.service_tag_mappings { + request = request.add_tags(service, tag_list_to_string_list(tags)); + } + request = request.add_filter_tags(tag_list_to_string_list(self.filter_tags)); + if let Some(page) = self.page { + request = match page { + PageIdentifier::Name(n) => request.destination_page_name(n), + PageIdentifier::Key(k) => request.destination_page_key(k), + }; + } + request = request.show_destination_page(self.show_page); + + let response = self.client.add_url(request.build()).await?; + + Ok(Url { + url: self.url, + normalised_url: Some(response.normalised_url), + }) + } +} diff --git a/src/models/builders/mod.rs b/src/models/builders/mod.rs index e69de29..8645e63 100644 --- a/src/models/builders/mod.rs +++ b/src/models/builders/mod.rs @@ -0,0 +1 @@ +pub mod import_builder; diff --git a/src/models/hydrus.rs b/src/models/hydrus.rs index 19137ee..270380a 100644 --- a/src/models/hydrus.rs +++ b/src/models/hydrus.rs @@ -1,3 +1,4 @@ +use crate::builders::import_builder::ImportBuilder; use crate::error::Result; use crate::models::version::Version; use crate::service::Services; @@ -28,4 +29,11 @@ impl Hydrus { Ok(Services::from_response(self.client.clone(), response)) } + + /// Creates an import builder to build an import request to hydrus + pub fn import(&mut self) -> ImportBuilder { + ImportBuilder { + client: self.client.clone(), + } + } } diff --git a/src/models/hydrus_file.rs b/src/models/hydrus_file.rs new file mode 100644 index 0000000..92beef7 --- /dev/null +++ b/src/models/hydrus_file.rs @@ -0,0 +1,8 @@ +use crate::endpoints::common::FileIdentifier; +use crate::Client; + +#[derive(Clone)] +pub struct HydrusFile { + pub(crate) client: Client, + pub id: FileIdentifier, +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 7a1c167..1b67080 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,4 +1,8 @@ pub mod builders; pub mod hydrus; +pub mod hydrus_file; +pub mod page; pub mod service; +pub mod tag; +pub mod url; pub mod version; diff --git a/src/models/page.rs b/src/models/page.rs new file mode 100644 index 0000000..de4e914 --- /dev/null +++ b/src/models/page.rs @@ -0,0 +1,20 @@ +#[derive(Clone)] +pub struct HydrusPage { + pub id: PageIdentifier, +} + +#[derive(Clone)] +pub enum PageIdentifier { + Name(String), + Key(String), +} + +impl PageIdentifier { + pub fn name(name: S) -> Self { + Self::Name(name.to_string()) + } + + pub fn key(key: S) -> Self { + Self::Key(key.to_string()) + } +} diff --git a/src/models/service.rs b/src/models/service.rs index a0f36d3..d39df71 100644 --- a/src/models/service.rs +++ b/src/models/service.rs @@ -56,10 +56,39 @@ impl ToString for ServiceType { } } +#[derive(Clone)] +pub struct ServiceName(pub String); + +impl ServiceName { + pub fn my_tags() -> Self { + Self(String::from("my tags")) + } + + pub fn my_files() -> Self { + Self(String::from("my files")) + } + + pub fn public_tag_repository() -> Self { + Self(String::from("public tag repository")) + } + + pub fn all_local_files() -> Self { + Self(String::from("all local files")) + } + + pub fn all_known_tags() -> Self { + Self(String::from("all known tags")) + } + + pub fn all_known_files() -> Self { + Self(String::from("all known files")) + } +} + #[derive(Clone)] pub struct Service { client: Client, - pub name: String, + pub name: ServiceName, pub key: String, pub service_type: ServiceType, } @@ -70,6 +99,7 @@ pub struct Services { } impl Services { + /// Creates the services list from a given hydrus response pub fn from_response(client: Client, response: GetServicesResponse) -> Self { let mut response = response.0; let mut mapped_types = HashMap::with_capacity(response.keys().len()); @@ -83,7 +113,7 @@ impl Services { for basic_service in basic_services { service_list.push(Service { service_type: mapped_type.clone(), - name: basic_service.name, + name: ServiceName(basic_service.name), key: basic_service.service_key, client: client.clone(), }) diff --git a/src/models/tag.rs b/src/models/tag.rs new file mode 100644 index 0000000..85ab7d1 --- /dev/null +++ b/src/models/tag.rs @@ -0,0 +1,35 @@ +#[derive(Clone, Debug)] +pub struct Tag { + pub name: String, + pub namespace: Option, +} + +impl From for Tag +where + S: AsRef, +{ + fn from(value: S) -> Self { + let value = value.as_ref(); + if let Some((namespace, tag)) = value.split_once(":") { + Self { + namespace: Some(namespace.to_string()), + name: tag.to_string(), + } + } else { + Self { + name: value.to_string(), + namespace: None, + } + } + } +} + +impl ToString for Tag { + fn to_string(&self) -> String { + if let Some(namespace) = &self.namespace { + format!("{}:{}", namespace, self.name) + } else { + self.name.clone() + } + } +} diff --git a/src/models/url.rs b/src/models/url.rs new file mode 100644 index 0000000..027fcbf --- /dev/null +++ b/src/models/url.rs @@ -0,0 +1,5 @@ +#[derive(Clone)] +pub struct Url { + pub url: String, + pub normalised_url: Option, +} diff --git a/src/utils.rs b/src/utils.rs index da0e527..b06ba26 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,5 @@ +use crate::models::tag::Tag; + pub fn string_list_to_json_array(l: Vec) -> String { format!("[\"{}\"]", l.join("\",\"")) } @@ -12,3 +14,8 @@ pub fn number_list_to_json_array(l: Vec) -> String { )) ) } + +/// Converts a list of tags into a list of string tags +pub fn tag_list_to_string_list(tags: Vec) -> Vec { + tags.into_iter().map(|t| t.to_string()).collect() +} diff --git a/tests/wrapper/mod.rs b/tests/wrapper/mod.rs index bcb74a6..f418e02 100644 --- a/tests/wrapper/mod.rs +++ b/tests/wrapper/mod.rs @@ -1 +1,2 @@ mod test_hydrus; +mod test_import; diff --git a/tests/wrapper/test_import.rs b/tests/wrapper/test_import.rs new file mode 100644 index 0000000..0a6bfbf --- /dev/null +++ b/tests/wrapper/test_import.rs @@ -0,0 +1,48 @@ +use super::super::common; +use hydrus_api::builders::import_builder::FileImport; +use hydrus_api::page::PageIdentifier; +use hydrus_api::service::ServiceName; +use hydrus_api::tag::Tag; + +#[tokio::test] +async fn it_imports_file_paths() { + let mut hydrus = common::get_hydrus(); + let result = hydrus + .import() + .file(FileImport::path("/does/not/exist/sadly")) + .run() + .await; + + assert!(result.is_err()) // file does not exist +} + +#[tokio::test] +async fn it_imports_binary_files() { + let mut hydrus = common::get_hydrus(); + let bytes = [0u8, 0u8, 0u8, 0u8]; + let result = hydrus + .import() + .file(FileImport::binary(&mut &bytes[..])) + .run() + .await; + + assert!(result.is_err()) // return status should be 4 +} + +#[tokio::test] +async fn it_imports_urls() { + let mut hydrus = common::get_hydrus(); + + let result = hydrus + .import() + .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") + .set_page(PageIdentifier::name("Rusty Import")) + .show_page(true) + .add_additional_tag(ServiceName::my_tags(), Tag::from("ark mage")) + .add_additional_tag(ServiceName::my_tags(), Tag::from("character:megumin")) + .run() + .await + .unwrap(); + + assert!(result.normalised_url.is_some()) // because it's returned by the import +} From fe01a4ae5c4a0d3355a8f72976bd67ef30ad026f Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 11 Jul 2021 15:40:14 +0200 Subject: [PATCH 04/12] Add importing functions to url type Signed-off-by: trivernis --- src/endpoints/adding_urls.rs | 2 +- src/models/builders/import_builder.rs | 59 +++++++++++++++------------ src/models/hydrus.rs | 16 ++++++++ src/models/hydrus_file.rs | 32 +++++++++++++++ src/models/url.rs | 58 +++++++++++++++++++++++++- tests/wrapper/mod.rs | 1 + tests/wrapper/test_hydrus.rs | 12 ++++++ tests/wrapper/test_import.rs | 6 ++- tests/wrapper/test_url.rs | 25 ++++++++++++ 9 files changed, 182 insertions(+), 29 deletions(-) create mode 100644 tests/wrapper/test_url.rs diff --git a/src/endpoints/adding_urls.rs b/src/endpoints/adding_urls.rs index 5b3068b..9c6432c 100644 --- a/src/endpoints/adding_urls.rs +++ b/src/endpoints/adding_urls.rs @@ -16,7 +16,7 @@ pub struct GetUrlFilesResponse { #[derive(Clone, Debug, Deserialize)] pub struct UrlFileStatus { - pub status: u32, + pub status: u8, pub hash: String, pub note: String, } diff --git a/src/models/builders/import_builder.rs b/src/models/builders/import_builder.rs index dbf8ff6..66442c7 100644 --- a/src/models/builders/import_builder.rs +++ b/src/models/builders/import_builder.rs @@ -1,6 +1,5 @@ use crate::endpoints::adding_files::{STATUS_IMPORT_FAILED, STATUS_IMPORT_VETOED}; use crate::endpoints::adding_urls::AddUrlRequestBuilder; -use crate::endpoints::common::FileIdentifier; use crate::error::{Error, Result}; use crate::hydrus_file::HydrusFile; use crate::models::url::Url; @@ -25,14 +24,7 @@ impl ImportBuilder { } pub fn url(self, url: S) -> UrlImportBuilder { - UrlImportBuilder { - client: self.client, - url: url.to_string(), - page: None, - show_page: false, - filter_tags: vec![], - service_tag_mappings: Default::default(), - } + UrlImportBuilder::new(self.client.clone(), url) } } @@ -58,15 +50,6 @@ pub struct FileImportBuilder { file: FileImport, } -pub struct UrlImportBuilder { - client: Client, - url: String, - page: Option, - show_page: bool, - filter_tags: Vec, - service_tag_mappings: HashMap>, -} - impl FileImportBuilder { pub async fn run(mut self) -> Result { let response = match self.file { @@ -79,17 +62,38 @@ impl FileImportBuilder { } else if response.status == STATUS_IMPORT_VETOED { Err(Error::ImportVetoed(response.note)) } else { - Ok(HydrusFile { - client: self.client, - id: FileIdentifier::Hash(response.hash), - }) + Ok(HydrusFile::from_raw_status_and_hash( + self.client, + response.status, + response.hash, + )) } } } +pub struct UrlImportBuilder { + client: Client, + url: String, + page: Option, + show_page: bool, + filter_tags: Vec, + service_tag_mappings: HashMap>, +} + impl UrlImportBuilder { + pub fn new(client: Client, url: S) -> Self { + Self { + client, + url: url.to_string(), + page: None, + show_page: false, + filter_tags: vec![], + service_tag_mappings: Default::default(), + } + } + /// Sets the destination page of the import - pub fn set_page(mut self, page: PageIdentifier) -> Self { + pub fn page(mut self, page: PageIdentifier) -> Self { self.page = Some(page); self @@ -134,7 +138,7 @@ impl UrlImportBuilder { /// Imports the URL pub async fn run(mut self) -> Result { - let mut request = AddUrlRequestBuilder::default().url(self.url.clone()); + let mut request = AddUrlRequestBuilder::default().url(&self.url); for (service, tags) in self.service_tag_mappings { request = request.add_tags(service, tag_list_to_string_list(tags)); @@ -149,10 +153,15 @@ impl UrlImportBuilder { request = request.show_destination_page(self.show_page); let response = self.client.add_url(request.build()).await?; + let url_info = self.client.get_url_info(&self.url).await?; Ok(Url { url: self.url, - normalised_url: Some(response.normalised_url), + client: self.client, + normalised_url: response.normalised_url, + url_type: url_info.url_type.into(), + match_name: url_info.match_name, + can_parse: url_info.can_parse, }) } } diff --git a/src/models/hydrus.rs b/src/models/hydrus.rs index 270380a..4522a37 100644 --- a/src/models/hydrus.rs +++ b/src/models/hydrus.rs @@ -1,5 +1,6 @@ use crate::builders::import_builder::ImportBuilder; use crate::error::Result; +use crate::models::url::Url; use crate::models::version::Version; use crate::service::Services; use crate::Client; @@ -36,4 +37,19 @@ impl Hydrus { client: self.client.clone(), } } + + /// Returns information about a given url in an object that allows + /// further operations with that url + pub async fn url>(&mut self, url: S) -> Result { + let info = self.client.get_url_info(&url).await?; + + Ok(Url { + client: self.client.clone(), + normalised_url: info.normalised_url, + url_type: info.url_type.into(), + match_name: info.match_name, + url: url.as_ref().to_string(), + can_parse: info.can_parse, + }) + } } diff --git a/src/models/hydrus_file.rs b/src/models/hydrus_file.rs index 92beef7..6c7af26 100644 --- a/src/models/hydrus_file.rs +++ b/src/models/hydrus_file.rs @@ -1,8 +1,40 @@ use crate::endpoints::common::FileIdentifier; use crate::Client; +#[derive(Clone, Debug, PartialOrd, PartialEq)] +pub enum FileStatus { + ReadyForImport, + InDatabase, + Deleted, + Unknown, +} + +impl Eq for FileStatus {} + #[derive(Clone)] pub struct HydrusFile { pub(crate) client: Client, pub id: FileIdentifier, + pub status: FileStatus, +} + +impl HydrusFile { + pub(crate) fn from_raw_status_and_hash( + client: Client, + status: u8, + hash: S, + ) -> Self { + let status = if status == 3 { + FileStatus::Deleted + } else if status == 0 { + FileStatus::ReadyForImport + } else { + FileStatus::InDatabase + }; + Self { + client, + id: FileIdentifier::Hash(hash.to_string()), + status, + } + } } diff --git a/src/models/url.rs b/src/models/url.rs index 027fcbf..71363d0 100644 --- a/src/models/url.rs +++ b/src/models/url.rs @@ -1,5 +1,61 @@ +use crate::builders::import_builder::UrlImportBuilder; +use crate::endpoints::adding_urls::{ + URL_TYPE_FILE, URL_TYPE_GALLERY, URL_TYPE_POST, URL_TYPE_WATCHABLE, +}; +use crate::error::Result; +use crate::hydrus_file::HydrusFile; +use crate::Client; + +#[derive(Clone, Debug, PartialOrd, PartialEq)] +pub enum UrlType { + Post, + File, + Gallery, + Watchable, + Unknown, +} + +impl Eq for UrlType {} + +impl From for UrlType { + fn from(value: u8) -> Self { + match value { + v if v == URL_TYPE_POST => Self::Post, + v if v == URL_TYPE_FILE => Self::File, + v if v == URL_TYPE_GALLERY => Self::Gallery, + v if v == URL_TYPE_WATCHABLE => Self::Watchable, + _ => Self::Unknown, + } + } +} + #[derive(Clone)] pub struct Url { pub url: String, - pub normalised_url: Option, + pub(crate) client: Client, + pub normalised_url: String, + pub url_type: UrlType, + pub match_name: String, + pub can_parse: bool, +} + +impl Url { + /// Returns a list of files associated with the url + pub async fn files(&mut self) -> Result> { + let response = self.client.get_url_files(&self.url).await?; + let files = response + .url_file_statuses + .into_iter() + .map(|file| { + HydrusFile::from_raw_status_and_hash(self.client.clone(), file.status, file.hash) + }) + .collect(); + + Ok(files) + } + + /// Creates an import builder for the url + pub fn import(&mut self) -> UrlImportBuilder { + UrlImportBuilder::new(self.client.clone(), &self.url) + } } diff --git a/tests/wrapper/mod.rs b/tests/wrapper/mod.rs index f418e02..fe956cc 100644 --- a/tests/wrapper/mod.rs +++ b/tests/wrapper/mod.rs @@ -1,2 +1,3 @@ mod test_hydrus; mod test_import; +mod test_url; diff --git a/tests/wrapper/test_hydrus.rs b/tests/wrapper/test_hydrus.rs index 4f46687..f9ec065 100644 --- a/tests/wrapper/test_hydrus.rs +++ b/tests/wrapper/test_hydrus.rs @@ -1,5 +1,6 @@ use super::super::common; use hydrus_api::service::ServiceType; +use hydrus_api::url::UrlType; #[tokio::test] async fn it_retrieves_version_info() { @@ -18,3 +19,14 @@ async fn it_retrieves_services() { assert!(services.get_services(ServiceType::AllKnownFiles).len() > 0); assert!(services.get_services(ServiceType::AllKnownTags).len() > 0); } + +#[tokio::test] +async fn it_retrieves_url_information() { + let mut hydrus = common::get_hydrus(); + let url = hydrus + .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") + .await + .unwrap(); + + assert_eq!(url.url_type, UrlType::Post) +} diff --git a/tests/wrapper/test_import.rs b/tests/wrapper/test_import.rs index 0a6bfbf..f1e2140 100644 --- a/tests/wrapper/test_import.rs +++ b/tests/wrapper/test_import.rs @@ -3,6 +3,7 @@ use hydrus_api::builders::import_builder::FileImport; use hydrus_api::page::PageIdentifier; use hydrus_api::service::ServiceName; use hydrus_api::tag::Tag; +use hydrus_api::url::UrlType; #[tokio::test] async fn it_imports_file_paths() { @@ -36,7 +37,7 @@ async fn it_imports_urls() { let result = hydrus .import() .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") - .set_page(PageIdentifier::name("Rusty Import")) + .page(PageIdentifier::name("Rusty Import")) .show_page(true) .add_additional_tag(ServiceName::my_tags(), Tag::from("ark mage")) .add_additional_tag(ServiceName::my_tags(), Tag::from("character:megumin")) @@ -44,5 +45,6 @@ async fn it_imports_urls() { .await .unwrap(); - assert!(result.normalised_url.is_some()) // because it's returned by the import + assert!(result.normalised_url.len() > 0); + assert_eq!(result.url_type, UrlType::Post) } diff --git a/tests/wrapper/test_url.rs b/tests/wrapper/test_url.rs new file mode 100644 index 0000000..89122f4 --- /dev/null +++ b/tests/wrapper/test_url.rs @@ -0,0 +1,25 @@ +use super::super::common; +use hydrus_api::page::PageIdentifier; +use hydrus_api::service::ServiceName; +use hydrus_api::tag::Tag; +use hydrus_api::url::Url; + +async fn get_url() -> Url { + let mut hydrus = common::get_hydrus(); + hydrus + .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") + .await + .unwrap() +} + +#[tokio::test] +async fn it_imports() { + let mut url = get_url().await; + + url.import() + .page(PageIdentifier::name("Rusty Import")) + .add_additional_tag(ServiceName::my_tags(), Tag::from("character:megumin")) + .run() + .await + .unwrap(); +} From 188da6c4c170af7093f0a49d0cfbb118d762e07b Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 11 Jul 2021 15:55:48 +0200 Subject: [PATCH 05/12] Add association function to urls Signed-off-by: trivernis --- src/models/url.rs | 14 ++++++++++++++ tests/wrapper/test_url.rs | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/models/url.rs b/src/models/url.rs index 71363d0..5e124e3 100644 --- a/src/models/url.rs +++ b/src/models/url.rs @@ -58,4 +58,18 @@ impl Url { pub fn import(&mut self) -> UrlImportBuilder { UrlImportBuilder::new(self.client.clone(), &self.url) } + + /// Associates the url with a list of file hashes + pub async fn associate(&mut self, hashes: Vec) -> Result<()> { + self.client + .associate_urls(vec![self.url.clone()], hashes) + .await + } + + /// Disassociates the url with a list of file hashes + pub async fn disassociate(&mut self, hashes: Vec) -> Result<()> { + self.client + .disassociate_urls(vec![self.url.clone()], hashes) + .await + } } diff --git a/tests/wrapper/test_url.rs b/tests/wrapper/test_url.rs index 89122f4..7df9806 100644 --- a/tests/wrapper/test_url.rs +++ b/tests/wrapper/test_url.rs @@ -23,3 +23,25 @@ async fn it_imports() { .await .unwrap(); } + +#[tokio::test] +async fn it_associates() { + let mut url = get_url().await; + + url.associate(vec![ + "0000000000000000000000000000000000000000000000000000000000000000".to_string(), + ]) + .await + .unwrap(); +} + +#[tokio::test] +async fn it_disassociates() { + let mut url = get_url().await; + + url.disassociate(vec![ + "0000000000000000000000000000000000000000000000000000000000000000".to_string(), + ]) + .await + .unwrap(); +} From 7d6b2bfe96c1addce02867ac8181d80434954198 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 11 Jul 2021 17:12:36 +0200 Subject: [PATCH 06/12] Add functions to files to retrieve information, associate urls and get tags Signed-off-by: trivernis --- src/client.rs | 90 +++++++++------- src/endpoints/access_management.rs | 8 +- src/endpoints/adding_files.rs | 10 +- src/endpoints/adding_tags.rs | 4 +- src/endpoints/adding_urls.rs | 8 +- src/endpoints/common.rs | 18 ++-- src/endpoints/mod.rs | 4 +- src/endpoints/searching_and_fetching_files.rs | 10 +- src/error.rs | 8 +- src/models/builders/import_builder.rs | 4 +- src/models/hydrus.rs | 20 +++- src/models/hydrus_file.rs | 102 +++++++++++++++++- src/models/service.rs | 4 +- tests/client/test_access_management.rs | 8 +- tests/client/test_adding_files.rs | 12 +-- tests/client/test_adding_tags.rs | 4 +- tests/client/test_adding_urls.rs | 10 +- .../test_searching_and_fetching_files.rs | 12 +-- tests/wrapper/mod.rs | 1 + tests/wrapper/test_files.rs | 49 +++++++++ tests/wrapper/test_hydrus.rs | 6 +- tests/wrapper/test_import.rs | 6 +- tests/wrapper/test_url.rs | 2 +- 23 files changed, 292 insertions(+), 108 deletions(-) create mode 100644 tests/wrapper/test_files.rs diff --git a/src/client.rs b/src/client.rs index fc162ab..1e9f2c2 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,7 +11,7 @@ use crate::endpoints::adding_urls::{ AddUrl, AddUrlRequest, AddUrlResponse, AssociateUrl, AssociateUrlRequest, GetUrlFiles, GetUrlFilesResponse, GetUrlInfo, GetUrlInfoResponse, }; -use crate::endpoints::common::{FileIdentifier, FileRecord}; +use crate::endpoints::common::{FileIdentifier, FileMetadataInfo, FileRecord}; use crate::endpoints::searching_and_fetching_files::{ FileMetadata, FileMetadataResponse, FileSearchLocation, GetFile, SearchFiles, SearchFilesResponse, @@ -43,10 +43,10 @@ impl Client { } /// Starts a get request to the path - async fn get(&mut self, query: &Q) -> Result { + async fn get(&self, query: &Q) -> Result { let response = self .inner - .get(format!("{}/{}", self.base_url, E::get_path())) + .get(format!("{}/{}", self.base_url, E::path())) .header(ACCESS_KEY_HEADER, &self.access_key) .query(query) .send() @@ -57,7 +57,7 @@ impl Client { /// Starts a get request to the path associated with the Endpoint Type async fn get_and_parse( - &mut self, + &self, query: &Q, ) -> Result { let response = self.get::(query).await?; @@ -66,10 +66,10 @@ impl Client { } /// Stats a post request to the path associated with the Endpoint Type - async fn post(&mut self, body: E::Request) -> Result { + async fn post(&self, body: E::Request) -> Result { let response = self .inner - .post(format!("{}/{}", self.base_url, E::get_path())) + .post(format!("{}/{}", self.base_url, E::path())) .json(&body) .header(ACCESS_KEY_HEADER, &self.access_key) .send() @@ -79,17 +79,17 @@ impl Client { } /// Stats a post request and parses the body as json - async fn post_and_parse(&mut self, body: E::Request) -> Result { + async fn post_and_parse(&self, body: E::Request) -> Result { let response = self.post::(body).await?; Self::extract_content(response).await } /// Stats a post request to the path associated with the return type - async fn post_binary(&mut self, data: Vec) -> Result { + async fn post_binary(&self, data: Vec) -> Result { let response = self .inner - .post(format!("{}/{}", self.base_url, E::get_path())) + .post(format!("{}/{}", self.base_url, E::path())) .body(data) .header(ACCESS_KEY_HEADER, &self.access_key) .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. - pub async fn api_version(&mut self) -> Result { + pub async fn api_version(&self) -> Result { self.get_and_parse::(&()).await } /// Creates a new session key - pub async fn session_key(&mut self) -> Result { + pub async fn session_key(&self) -> Result { self.get_and_parse::(&()).await } /// Verifies if the access key is valid and returns some information about its permissions - pub async fn verify_access_key(&mut self) -> Result { + pub async fn verify_access_key(&self) -> Result { self.get_and_parse::(&()).await } /// Returns the list of tag and file services of the client - pub async fn get_services(&mut self) -> Result { + pub async fn get_services(&self) -> Result { self.get_and_parse::(&()).await } /// Adds a file to hydrus - pub async fn add_file>(&mut self, path: S) -> Result { + pub async fn add_file>(&self, path: S) -> Result { self.post_and_parse::(AddFileRequest { path: path.as_ref().to_string(), }) @@ -144,12 +144,12 @@ impl Client { } /// Adds a file from binary data to hydrus - pub async fn add_binary_file(&mut self, data: Vec) -> Result { + pub async fn add_binary_file(&self, data: Vec) -> Result { self.post_binary::(data).await } /// Moves files with matching hashes to the trash - pub async fn delete_files(&mut self, hashes: Vec) -> Result<()> { + pub async fn delete_files(&self, hashes: Vec) -> Result<()> { self.post::(DeleteFilesRequest { hashes }) .await?; @@ -157,7 +157,7 @@ impl Client { } /// Pulls files out of the trash by hash - pub async fn undelete_files(&mut self, hashes: Vec) -> Result<()> { + pub async fn undelete_files(&self, hashes: Vec) -> Result<()> { self.post::(UndeleteFilesRequest { hashes }) .await?; @@ -165,7 +165,7 @@ impl Client { } /// Moves files from the inbox into the archive - pub async fn archive_files(&mut self, hashes: Vec) -> Result<()> { + pub async fn archive_files(&self, hashes: Vec) -> Result<()> { self.post::(ArchiveFilesRequest { hashes }) .await?; @@ -173,7 +173,7 @@ impl Client { } /// Moves files from the archive into the inbox - pub async fn unarchive_files(&mut self, hashes: Vec) -> Result<()> { + pub async fn unarchive_files(&self, hashes: Vec) -> Result<()> { self.post::(UnarchiveFilesRequest { hashes }) .await?; @@ -181,7 +181,7 @@ impl Client { } /// 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) -> Result { + pub async fn clean_tags(&self, tags: Vec) -> Result { self.get_and_parse::(&[( "tags", string_list_to_json_array(tags), @@ -190,7 +190,7 @@ impl Client { } /// 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::(request).await?; Ok(()) @@ -198,7 +198,7 @@ impl Client { /// Searches for files in the inbox, the archive or both pub async fn search_files( - &mut self, + &self, tags: Vec, location: FileSearchLocation, ) -> Result { @@ -212,19 +212,37 @@ impl Client { /// Returns the metadata for a given list of file_ids or hashes pub async fn get_file_metadata( - &mut self, + &self, file_ids: Vec, hashes: Vec, ) -> Result { - self.get_and_parse::(&[ - ("file_ids", number_list_to_json_array(file_ids)), - ("hashes", string_list_to_json_array(hashes)), - ]) - .await + let query = if file_ids.len() > 0 { + ("file_ids", number_list_to_json_array(file_ids)) + } else { + ("hashes", string_list_to_json_array(hashes)) + }; + self.get_and_parse::(&[query]) + .await + } + + /// Returns the metadata for a single file identifier + pub async fn get_file_metadata_by_identifier( + &self, + identifier: FileIdentifier, + ) -> Result { + 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 - pub async fn get_file(&mut self, id: FileIdentifier) -> Result { + pub async fn get_file(&self, id: FileIdentifier) -> Result { let response = match id { FileIdentifier::ID(id) => { self.get::(&[("file_id", id)]) @@ -248,24 +266,24 @@ impl Client { } /// Returns all files associated with the given url - pub async fn get_url_files>(&mut self, url: S) -> Result { + pub async fn get_url_files>(&self, url: S) -> Result { self.get_and_parse::(&[("url", url.as_ref())]) .await } /// Returns information about the given url - pub async fn get_url_info>(&mut self, url: S) -> Result { + pub async fn get_url_info>(&self, url: S) -> Result { self.get_and_parse::(&[("url", url.as_ref())]) .await } /// Adds an url to hydrus, optionally with additional tags and a destination page - pub async fn add_url(&mut self, request: AddUrlRequest) -> Result { + pub async fn add_url(&self, request: AddUrlRequest) -> Result { self.post_and_parse::(request).await } /// Associates urls with the given file hashes - pub async fn associate_urls(&mut self, urls: Vec, hashes: Vec) -> Result<()> { + pub async fn associate_urls(&self, urls: Vec, hashes: Vec) -> Result<()> { self.post::(AssociateUrlRequest { hashes, urls_to_add: urls, @@ -277,11 +295,7 @@ impl Client { } /// Disassociates urls with the given file hashes - pub async fn disassociate_urls( - &mut self, - urls: Vec, - hashes: Vec, - ) -> Result<()> { + pub async fn disassociate_urls(&self, urls: Vec, hashes: Vec) -> Result<()> { self.post::(AssociateUrlRequest { hashes, urls_to_add: vec![], diff --git a/src/endpoints/access_management.rs b/src/endpoints/access_management.rs index 6f2597d..d7c55ad 100644 --- a/src/endpoints/access_management.rs +++ b/src/endpoints/access_management.rs @@ -23,7 +23,7 @@ impl Endpoint for ApiVersion { type Request = (); type Response = ApiVersionResponse; - fn get_path() -> String { + fn path() -> String { String::from("api_version") } } @@ -39,7 +39,7 @@ impl Endpoint for SessionKey { type Request = (); type Response = SessionKeyResponse; - fn get_path() -> String { + fn path() -> String { String::from("session_key") } } @@ -56,7 +56,7 @@ impl Endpoint for VerifyAccessKey { type Request = (); type Response = VerifyAccessKeyResponse; - fn get_path() -> String { + fn path() -> String { String::from("verify_access_key") } } @@ -70,7 +70,7 @@ impl Endpoint for GetServices { type Request = (); type Response = GetServicesResponse; - fn get_path() -> String { + fn path() -> String { String::from("get_services") } } diff --git a/src/endpoints/adding_files.rs b/src/endpoints/adding_files.rs index 09da721..412cf09 100644 --- a/src/endpoints/adding_files.rs +++ b/src/endpoints/adding_files.rs @@ -25,7 +25,7 @@ impl Endpoint for AddFile { type Request = AddFileRequest; type Response = AddFileResponse; - fn get_path() -> String { + fn path() -> String { String::from("add_files/add_file") } } @@ -38,7 +38,7 @@ impl Endpoint for DeleteFiles { type Request = DeleteFilesRequest; type Response = (); - fn get_path() -> String { + fn path() -> String { String::from("add_files/delete_files") } } @@ -50,7 +50,7 @@ impl Endpoint for UndeleteFiles { type Request = UndeleteFilesRequest; type Response = (); - fn get_path() -> String { + fn path() -> String { String::from("add_files/undelete_files") } } @@ -62,7 +62,7 @@ impl Endpoint for ArchiveFiles { type Request = ArchiveFilesRequest; type Response = (); - fn get_path() -> String { + fn path() -> String { String::from("add_files/archive_files") } } @@ -74,7 +74,7 @@ impl Endpoint for UnarchiveFiles { type Request = UndeleteFilesRequest; type Response = (); - fn get_path() -> String { + fn path() -> String { String::from("add_files/unarchive_files") } } diff --git a/src/endpoints/adding_tags.rs b/src/endpoints/adding_tags.rs index 04c09d8..c78f396 100644 --- a/src/endpoints/adding_tags.rs +++ b/src/endpoints/adding_tags.rs @@ -12,7 +12,7 @@ impl Endpoint for CleanTags { type Request = (); type Response = CleanTagsResponse; - fn get_path() -> String { + fn path() -> String { String::from("add_tags/clean_tags") } } @@ -30,7 +30,7 @@ impl Endpoint for AddTags { type Request = AddTagsRequest; type Response = (); - fn get_path() -> String { + fn path() -> String { String::from("add_tags/add_tags") } } diff --git a/src/endpoints/adding_urls.rs b/src/endpoints/adding_urls.rs index 9c6432c..094262b 100644 --- a/src/endpoints/adding_urls.rs +++ b/src/endpoints/adding_urls.rs @@ -27,7 +27,7 @@ impl Endpoint for GetUrlFiles { type Request = (); type Response = GetUrlFilesResponse; - fn get_path() -> String { + fn path() -> String { String::from("add_urls/get_url_files") } } @@ -47,7 +47,7 @@ impl Endpoint for GetUrlInfo { type Request = (); type Response = GetUrlInfoResponse; - fn get_path() -> String { + fn path() -> String { String::from("add_urls/get_url_info") } } @@ -164,7 +164,7 @@ impl Endpoint for AddUrl { type Request = AddUrlRequest; type Response = AddUrlResponse; - fn get_path() -> String { + fn path() -> String { String::from("add_urls/add_url") } } @@ -181,7 +181,7 @@ impl Endpoint for AssociateUrl { type Request = AssociateUrlRequest; type Response = (); - fn get_path() -> String { + fn path() -> String { String::from("add_urls/associate_url") } } diff --git a/src/endpoints/common.rs b/src/endpoints/common.rs index f3d368d..ac9fb65 100644 --- a/src/endpoints/common.rs +++ b/src/endpoints/common.rs @@ -11,17 +11,17 @@ pub struct BasicHashList { pub hashes: Vec, } -#[derive(Clone, Default, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct FileMetadataInfo { pub file_id: u64, pub hash: String, - pub size: u64, + pub size: Option, pub mime: String, pub ext: String, - pub width: u32, - pub height: u32, + pub width: Option, + pub height: Option, pub duration: Option, - pub has_audio: bool, + pub has_audio: Option, pub num_frames: Option, pub num_words: Option, pub is_inbox: bool, @@ -32,12 +32,18 @@ pub struct FileMetadataInfo { pub service_names_to_statuses_to_display_tags: HashMap>>, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum FileIdentifier { ID(u64), Hash(String), } +impl FileIdentifier { + pub fn hash(hash: S) -> Self { + Self::Hash(hash.to_string()) + } +} + #[derive(Clone)] pub struct FileRecord { pub bytes: Vec, diff --git a/src/endpoints/mod.rs b/src/endpoints/mod.rs index 13cda47..468521c 100644 --- a/src/endpoints/mod.rs +++ b/src/endpoints/mod.rs @@ -8,9 +8,9 @@ pub mod adding_urls; pub mod common; pub mod searching_and_fetching_files; -pub trait Endpoint { +pub(crate) trait Endpoint { type Request: Serialize; type Response: DeserializeOwned; - fn get_path() -> String; + fn path() -> String; } diff --git a/src/endpoints/searching_and_fetching_files.rs b/src/endpoints/searching_and_fetching_files.rs index aa07178..5739587 100644 --- a/src/endpoints/searching_and_fetching_files.rs +++ b/src/endpoints/searching_and_fetching_files.rs @@ -44,14 +44,14 @@ impl Endpoint for SearchFiles { type Request = (); type Response = SearchFilesResponse; - fn get_path() -> String { + fn path() -> String { String::from("get_files/search_files") } } -#[derive(Clone, Default, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize)] pub struct FileMetadataResponse { - metadata: Vec, + pub metadata: Vec, } pub struct FileMetadata; @@ -60,7 +60,7 @@ impl Endpoint for FileMetadata { type Request = (); type Response = FileMetadataResponse; - fn get_path() -> String { + fn path() -> String { String::from("get_files/file_metadata") } } @@ -71,7 +71,7 @@ impl Endpoint for GetFile { type Request = (); type Response = (); - fn get_path() -> String { + fn path() -> String { String::from("get_files/file") } } diff --git a/src/error.rs b/src/error.rs index 24010d0..c62f74e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,4 @@ +use crate::endpoints::common::FileIdentifier; use std::error::Error as StdError; use std::fmt; @@ -10,6 +11,7 @@ pub enum Error { InvalidServiceType(String), ImportVetoed(String), ImportFailed(String), + FileNotFound(FileIdentifier), } impl fmt::Display for Error { @@ -22,6 +24,7 @@ impl fmt::Display for Error { } Self::ImportFailed(msg) => write!(f, "File import failed: {}", 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)> { match self { Self::Reqwest(e) => e.source(), - Self::Hydrus(_) => None, - Self::InvalidServiceType(_) => None, - Self::ImportVetoed(_) => None, - Self::ImportFailed(_) => None, + _ => None, } } } diff --git a/src/models/builders/import_builder.rs b/src/models/builders/import_builder.rs index 66442c7..48c5e4b 100644 --- a/src/models/builders/import_builder.rs +++ b/src/models/builders/import_builder.rs @@ -51,7 +51,7 @@ pub struct FileImportBuilder { } impl FileImportBuilder { - pub async fn run(mut self) -> Result { + pub async fn run(self) -> Result { let response = match self.file { FileImport::Path(path) => self.client.add_file(path).await?, FileImport::Binary(b) => self.client.add_binary_file(b).await?, @@ -137,7 +137,7 @@ impl UrlImportBuilder { } /// Imports the URL - pub async fn run(mut self) -> Result { + pub async fn run(self) -> Result { let mut request = AddUrlRequestBuilder::default().url(&self.url); for (service, tags) in self.service_tag_mappings { diff --git a/src/models/hydrus.rs b/src/models/hydrus.rs index 4522a37..bfc1fe0 100644 --- a/src/models/hydrus.rs +++ b/src/models/hydrus.rs @@ -1,5 +1,7 @@ use crate::builders::import_builder::ImportBuilder; +use crate::endpoints::common::FileIdentifier; use crate::error::Result; +use crate::hydrus_file::HydrusFile; use crate::models::url::Url; use crate::models::version::Version; use crate::service::Services; @@ -16,7 +18,7 @@ impl Hydrus { } /// Returns the Hydrus and API Version - pub async fn version(&mut self) -> Result { + pub async fn version(&self) -> Result { let response = self.client.api_version().await?; Ok(Version { api: response.version, @@ -25,14 +27,14 @@ impl Hydrus { } /// Returns a list of available services - pub async fn services(&mut self) -> Result { + pub async fn services(&self) -> Result { let response = self.client.get_services().await?; Ok(Services::from_response(self.client.clone(), response)) } /// Creates an import builder to build an import request to hydrus - pub fn import(&mut self) -> ImportBuilder { + pub fn import(&self) -> ImportBuilder { ImportBuilder { client: self.client.clone(), } @@ -40,7 +42,7 @@ impl Hydrus { /// Returns information about a given url in an object that allows /// further operations with that url - pub async fn url>(&mut self, url: S) -> Result { + pub async fn url>(&self, url: S) -> Result { let info = self.client.get_url_info(&url).await?; Ok(Url { @@ -52,4 +54,14 @@ impl Hydrus { can_parse: info.can_parse, }) } + + /// Returns a file by identifier to perform further operations on + pub async fn file(&self, identifier: FileIdentifier) -> Result { + let metadata = self + .client + .get_file_metadata_by_identifier(identifier) + .await?; + + Ok(HydrusFile::from_metadata(self.client.clone(), metadata)) + } } diff --git a/src/models/hydrus_file.rs b/src/models/hydrus_file.rs index 6c7af26..d60a37e 100644 --- a/src/models/hydrus_file.rs +++ b/src/models/hydrus_file.rs @@ -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 std::collections::HashMap; #[derive(Clone, Debug, PartialOrd, PartialEq)] pub enum FileStatus { @@ -16,6 +20,7 @@ pub struct HydrusFile { pub(crate) client: Client, pub id: FileIdentifier, pub status: FileStatus, + pub(crate) metadata: Option, } impl HydrusFile { @@ -35,6 +40,101 @@ impl HydrusFile { client, id: FileIdentifier::Hash(hash.to_string()), 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 { + 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) -> 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) -> 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>> { + 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> { + 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) + } } diff --git a/src/models/service.rs b/src/models/service.rs index d39df71..447213c 100644 --- a/src/models/service.rs +++ b/src/models/service.rs @@ -56,9 +56,11 @@ impl ToString for ServiceType { } } -#[derive(Clone)] +#[derive(Clone, PartialOrd, PartialEq, Hash)] pub struct ServiceName(pub String); +impl Eq for ServiceName {} + impl ServiceName { pub fn my_tags() -> Self { Self(String::from("my tags")) diff --git a/tests/client/test_access_management.rs b/tests/client/test_access_management.rs index 1834fca..0ff5101 100644 --- a/tests/client/test_access_management.rs +++ b/tests/client/test_access_management.rs @@ -2,7 +2,7 @@ use super::super::common; #[tokio::test] 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(); assert!(api_version.hydrus_version > 0); assert!(api_version.version > 0); @@ -10,14 +10,14 @@ async fn it_returns_the_api_version() { #[tokio::test] 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(); assert!(session_key.session_key.len() > 0); } #[tokio::test] 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(); 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); @@ -25,7 +25,7 @@ async fn it_verifies_the_access_key() { #[tokio::test] 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(); assert!(services_response.0.keys().len() > 0); } diff --git a/tests/client/test_adding_files.rs b/tests/client/test_adding_files.rs index aa12bcc..37b1a7e 100644 --- a/tests/client/test_adding_files.rs +++ b/tests/client/test_adding_files.rs @@ -2,14 +2,14 @@ use super::super::common; #[tokio::test] 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; assert!(result.is_err()); // because the path does not exist } #[tokio::test] async fn it_adds_binary_files() { - let mut client = common::get_client(); + let client = common::get_client(); let result = client .add_binary_file(vec![0u8, 0u8, 0u8, 0u8]) .await @@ -19,24 +19,24 @@ async fn it_adds_binary_files() { #[tokio::test] async fn it_deletes_files() { - let mut client = common::get_client(); + let client = common::get_client(); client.delete_files(vec![]).await.unwrap(); } #[tokio::test] async fn it_undeletes_files() { - let mut client = common::get_client(); + let client = common::get_client(); client.undelete_files(vec![]).await.unwrap(); } #[tokio::test] async fn it_archives_files() { - let mut client = common::get_client(); + let client = common::get_client(); client.archive_files(vec![]).await.unwrap(); } #[tokio::test] async fn it_unarchives_files() { - let mut client = common::get_client(); + let client = common::get_client(); client.unarchive_files(vec![]).await.unwrap(); } diff --git a/tests/client/test_adding_tags.rs b/tests/client/test_adding_tags.rs index 819cdc1..2bc6fa3 100644 --- a/tests/client/test_adding_tags.rs +++ b/tests/client/test_adding_tags.rs @@ -3,7 +3,7 @@ use hydrus_api::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; #[tokio::test] async fn it_cleans_tags() { - let mut client = common::get_client(); + let client = common::get_client(); let response = client .clean_tags(vec![ "summer".into(), @@ -18,7 +18,7 @@ async fn it_cleans_tags() { #[tokio::test] async fn it_adds_tags() { - let mut client = common::get_client(); + let client = common::get_client(); let request = AddTagsRequestBuilder::default() .add_hash("0000000000000000000000000000000000000000000000000000000000000000") // valid hash, I hope no files are affected .add_tags("my tags", vec!["beach".into(), "summer".into()]) diff --git a/tests/client/test_adding_urls.rs b/tests/client/test_adding_urls.rs index 01e5af0..ab6dd3b 100644 --- a/tests/client/test_adding_urls.rs +++ b/tests/client/test_adding_urls.rs @@ -3,7 +3,7 @@ use hydrus_api::endpoints::adding_urls::{AddUrlRequestBuilder, URL_TYPE_POST}; #[tokio::test] async fn it_returns_files_for_an_url() { - let mut client = common::get_client(); + let client = common::get_client(); let response = client .get_url_files("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .await @@ -14,7 +14,7 @@ async fn it_returns_files_for_an_url() { #[tokio::test] async fn it_returns_url_information() { - let mut client = common::get_client(); + let client = common::get_client(); let info = client .get_url_info("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .await @@ -25,7 +25,7 @@ async fn it_returns_url_information() { #[tokio::test] async fn it_adds_urls() { - let mut client = common::get_client(); + let client = common::get_client(); let request = AddUrlRequestBuilder::default() .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .add_tags( @@ -41,7 +41,7 @@ async fn it_adds_urls() { #[tokio::test] async fn it_associates_urls() { - let mut client = common::get_client(); + let client = common::get_client(); client .associate_urls( vec![ @@ -56,7 +56,7 @@ async fn it_associates_urls() { #[tokio::test] async fn it_disassociates_urls() { - let mut client = common::get_client(); + let client = common::get_client(); client .disassociate_urls( vec![ diff --git a/tests/client/test_searching_and_fetching_files.rs b/tests/client/test_searching_and_fetching_files.rs index 42d4940..db0ffaf 100644 --- a/tests/client/test_searching_and_fetching_files.rs +++ b/tests/client/test_searching_and_fetching_files.rs @@ -4,7 +4,7 @@ use hydrus_api::endpoints::searching_and_fetching_files::FileSearchLocation; #[tokio::test] async fn is_searches_files() { - let mut client = common::get_client(); + let client = common::get_client(); client .search_files(vec!["beach".to_string()], FileSearchLocation::Archive) .await @@ -13,19 +13,19 @@ async fn is_searches_files() { #[tokio::test] async fn it_fetches_file_metadata() { - let mut client = common::get_client(); - client + let client = common::get_client(); + let response = client .get_file_metadata( vec![], vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()], ) - .await - .unwrap(); + .await; + assert!(response.is_ok()); // Even if the file doesn't exist it still returns some information about it } #[tokio::test] async fn it_fetches_single_files() { - let mut client = common::get_client(); + let client = common::get_client(); let response = client .get_file(FileIdentifier::Hash( "0000000000000000000000000000000000000000000000000000000000000000".to_string(), diff --git a/tests/wrapper/mod.rs b/tests/wrapper/mod.rs index fe956cc..44e1c9a 100644 --- a/tests/wrapper/mod.rs +++ b/tests/wrapper/mod.rs @@ -1,3 +1,4 @@ +mod test_files; mod test_hydrus; mod test_import; mod test_url; diff --git a/tests/wrapper/test_files.rs b/tests/wrapper/test_files.rs new file mode 100644 index 0000000..48f2c83 --- /dev/null +++ b/tests/wrapper/test_files.rs @@ -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 +} diff --git a/tests/wrapper/test_hydrus.rs b/tests/wrapper/test_hydrus.rs index f9ec065..5e04b21 100644 --- a/tests/wrapper/test_hydrus.rs +++ b/tests/wrapper/test_hydrus.rs @@ -4,7 +4,7 @@ use hydrus_api::url::UrlType; #[tokio::test] async fn it_retrieves_version_info() { - let mut hydrus = common::get_hydrus(); + let hydrus = common::get_hydrus(); let version = hydrus.version().await.unwrap(); assert!(version.hydrus > 0); assert!(version.api > 0); @@ -12,7 +12,7 @@ async fn it_retrieves_version_info() { #[tokio::test] async fn it_retrieves_services() { - let mut hydrus = common::get_hydrus(); + let hydrus = common::get_hydrus(); let services = hydrus.services().await.unwrap(); // assuming hydrus is configured correctly @@ -22,7 +22,7 @@ async fn it_retrieves_services() { #[tokio::test] async fn it_retrieves_url_information() { - let mut hydrus = common::get_hydrus(); + let hydrus = common::get_hydrus(); let url = hydrus .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .await diff --git a/tests/wrapper/test_import.rs b/tests/wrapper/test_import.rs index f1e2140..39281c0 100644 --- a/tests/wrapper/test_import.rs +++ b/tests/wrapper/test_import.rs @@ -7,7 +7,7 @@ use hydrus_api::url::UrlType; #[tokio::test] async fn it_imports_file_paths() { - let mut hydrus = common::get_hydrus(); + let hydrus = common::get_hydrus(); let result = hydrus .import() .file(FileImport::path("/does/not/exist/sadly")) @@ -19,7 +19,7 @@ async fn it_imports_file_paths() { #[tokio::test] async fn it_imports_binary_files() { - let mut hydrus = common::get_hydrus(); + let hydrus = common::get_hydrus(); let bytes = [0u8, 0u8, 0u8, 0u8]; let result = hydrus .import() @@ -32,7 +32,7 @@ async fn it_imports_binary_files() { #[tokio::test] async fn it_imports_urls() { - let mut hydrus = common::get_hydrus(); + let hydrus = common::get_hydrus(); let result = hydrus .import() diff --git a/tests/wrapper/test_url.rs b/tests/wrapper/test_url.rs index 7df9806..cc97bd6 100644 --- a/tests/wrapper/test_url.rs +++ b/tests/wrapper/test_url.rs @@ -5,7 +5,7 @@ use hydrus_api::tag::Tag; use hydrus_api::url::Url; async fn get_url() -> Url { - let mut hydrus = common::get_hydrus(); + let hydrus = common::get_hydrus(); hydrus .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") .await From 43c5ac47eeba2d307734066bbf18ae40eabb07a0 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 11 Jul 2021 17:28:22 +0200 Subject: [PATCH 07/12] Add functions to add and modify tags to the file struct Signed-off-by: trivernis --- src/endpoints/adding_tags.rs | 1 + src/models/hydrus_file.rs | 31 +++++++++++++++++++++++++++++++ tests/wrapper/test_files.rs | 25 +++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/src/endpoints/adding_tags.rs b/src/endpoints/adding_tags.rs index c78f396..15357b8 100644 --- a/src/endpoints/adding_tags.rs +++ b/src/endpoints/adding_tags.rs @@ -42,6 +42,7 @@ pub struct AddTagsRequestBuilder { } /// List of actions for a given tag +#[derive(Clone, Debug)] pub enum TagAction { /// Add to a local tag service. AddToLocalService, diff --git a/src/models/hydrus_file.rs b/src/models/hydrus_file.rs index d60a37e..1a2a733 100644 --- a/src/models/hydrus_file.rs +++ b/src/models/hydrus_file.rs @@ -1,7 +1,9 @@ +use crate::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; use crate::endpoints::common::{FileIdentifier, FileMetadataInfo}; use crate::error::Result; use crate::service::ServiceName; use crate::tag::Tag; +use crate::utils::tag_list_to_string_list; use crate::Client; use std::collections::HashMap; @@ -137,4 +139,33 @@ impl HydrusFile { Ok(tag_list) } + + /// Adds tags for a specific service to the file + pub async fn add_tags(&mut self, service: ServiceName, tags: Vec) -> Result<()> { + let hash = self.hash().await?; + let request = AddTagsRequestBuilder::default() + .add_hash(hash) + .add_tags(service.0, tag_list_to_string_list(tags)) + .build(); + + self.client.add_tags(request).await + } + + /// Allows modification of tags by using the defined tag actions + pub async fn modify_tags( + &mut self, + service: ServiceName, + action: TagAction, + tags: Vec, + ) -> Result<()> { + let hash = self.hash().await?; + let mut reqwest = AddTagsRequestBuilder::default().add_hash(hash); + + for tag in tags { + reqwest = + reqwest.add_tag_with_action(service.0.clone(), tag.to_string(), action.clone()); + } + + self.client.add_tags(reqwest.build()).await + } } diff --git a/tests/wrapper/test_files.rs b/tests/wrapper/test_files.rs index 48f2c83..73291f6 100644 --- a/tests/wrapper/test_files.rs +++ b/tests/wrapper/test_files.rs @@ -1,6 +1,8 @@ use super::super::common; +use hydrus_api::endpoints::adding_tags::TagAction; use hydrus_api::endpoints::common::FileIdentifier; use hydrus_api::hydrus_file::HydrusFile; +use hydrus_api::service::ServiceName; async fn get_file() -> HydrusFile { let hydrus = common::get_hydrus(); @@ -47,3 +49,26 @@ async fn it_has_tags() { assert!(tags.len() > 0) // test data needs to be prepared this way } + +#[tokio::test] +async fn it_adds_tags() { + let mut file = get_file().await; + file.add_tags( + ServiceName::public_tag_repository(), + vec!["character:megumin".into(), "ark mage".into()], + ) + .await + .unwrap(); +} + +#[tokio::test] +async fn it_modifies_tags() { + let mut file = get_file().await; + file.modify_tags( + ServiceName::public_tag_repository(), + TagAction::RescindPendFromRepository, + vec!["ark mage".into()], + ) + .await + .unwrap(); +} From a73e7de5326b18dfa050611c425b7bf32a99fb76 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 11 Jul 2021 18:11:57 +0200 Subject: [PATCH 08/12] Add search functionality to hydrus wrapper Signed-off-by: trivernis --- src/endpoints/searching_and_fetching_files.rs | 11 +--------- src/models/hydrus.rs | 22 +++++++++++++++++++ src/models/hydrus_file.rs | 9 ++++++++ tests/wrapper/test_hydrus.rs | 13 +++++++++++ 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/endpoints/searching_and_fetching_files.rs b/src/endpoints/searching_and_fetching_files.rs index 5739587..3a46d66 100644 --- a/src/endpoints/searching_and_fetching_files.rs +++ b/src/endpoints/searching_and_fetching_files.rs @@ -7,7 +7,6 @@ pub struct SearchFilesResponse { } pub enum FileSearchLocation { - All, Inbox, Archive, } @@ -16,14 +15,6 @@ 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 } @@ -33,7 +24,7 @@ impl FileSearchLocation { if let &Self::Archive = &self { true } else { - self.is_all() + false } } } diff --git a/src/models/hydrus.rs b/src/models/hydrus.rs index bfc1fe0..038c136 100644 --- a/src/models/hydrus.rs +++ b/src/models/hydrus.rs @@ -1,10 +1,13 @@ use crate::builders::import_builder::ImportBuilder; use crate::endpoints::common::FileIdentifier; +use crate::endpoints::searching_and_fetching_files::FileSearchLocation; use crate::error::Result; use crate::hydrus_file::HydrusFile; use crate::models::url::Url; use crate::models::version::Version; use crate::service::Services; +use crate::tag::Tag; +use crate::utils::tag_list_to_string_list; use crate::Client; pub struct Hydrus { @@ -64,4 +67,23 @@ impl Hydrus { Ok(HydrusFile::from_metadata(self.client.clone(), metadata)) } + + /// Searches for files that have the given tags and returns a list of hydrus files as a result + pub async fn search( + &self, + location: FileSearchLocation, + tags: Vec, + ) -> Result> { + let search_result = self + .client + .search_files(tag_list_to_string_list(tags), location) + .await?; + let files = search_result + .file_ids + .into_iter() + .map(|id| HydrusFile::from_id(self.client.clone(), id)) + .collect(); + + Ok(files) + } } diff --git a/src/models/hydrus_file.rs b/src/models/hydrus_file.rs index 1a2a733..e128c8e 100644 --- a/src/models/hydrus_file.rs +++ b/src/models/hydrus_file.rs @@ -26,6 +26,15 @@ pub struct HydrusFile { } impl HydrusFile { + pub(crate) fn from_id(client: Client, id: u64) -> Self { + Self { + client, + id: FileIdentifier::ID(id), + status: FileStatus::Unknown, + metadata: None, + } + } + pub(crate) fn from_raw_status_and_hash( client: Client, status: u8, diff --git a/tests/wrapper/test_hydrus.rs b/tests/wrapper/test_hydrus.rs index 5e04b21..7ce11c9 100644 --- a/tests/wrapper/test_hydrus.rs +++ b/tests/wrapper/test_hydrus.rs @@ -1,4 +1,5 @@ use super::super::common; +use hydrus_api::endpoints::searching_and_fetching_files::FileSearchLocation; use hydrus_api::service::ServiceType; use hydrus_api::url::UrlType; @@ -30,3 +31,15 @@ async fn it_retrieves_url_information() { assert_eq!(url.url_type, UrlType::Post) } + +#[tokio::test] +async fn it_searches() { + let hydrus = common::get_hydrus(); + hydrus + .search( + FileSearchLocation::Archive, + vec!["character:megumin".into()], + ) + .await + .unwrap(); +} From d3b4093f9391a471595839950785de035db2f6f0 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 11 Jul 2021 20:54:43 +0200 Subject: [PATCH 09/12] Add retrieval of its bytes to the file struct Signed-off-by: trivernis --- src/models/hydrus_file.rs | 7 ++++++- tests/wrapper/test_files.rs | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/models/hydrus_file.rs b/src/models/hydrus_file.rs index e128c8e..51abc5a 100644 --- a/src/models/hydrus_file.rs +++ b/src/models/hydrus_file.rs @@ -1,5 +1,5 @@ use crate::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; -use crate::endpoints::common::{FileIdentifier, FileMetadataInfo}; +use crate::endpoints::common::{FileIdentifier, FileMetadataInfo, FileRecord}; use crate::error::Result; use crate::service::ServiceName; use crate::tag::Tag; @@ -177,4 +177,9 @@ impl HydrusFile { self.client.add_tags(reqwest.build()).await } + + /// Retrieves the file record bytes + pub async fn retrieve(&self) -> Result { + self.client.get_file(self.id.clone()).await + } } diff --git a/tests/wrapper/test_files.rs b/tests/wrapper/test_files.rs index 73291f6..1fccbc4 100644 --- a/tests/wrapper/test_files.rs +++ b/tests/wrapper/test_files.rs @@ -72,3 +72,11 @@ async fn it_modifies_tags() { .await .unwrap(); } + +#[tokio::test] +async fn it_retrieves_content() { + let file = get_file().await; + let file = file.retrieve().await.unwrap(); + + assert!(file.bytes.len() > 0) // assuming it exists +} From 9ca17151778d8870572328ea44b28b4006119b5a Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 15 Jul 2021 20:18:31 +0200 Subject: [PATCH 10/12] Add bulk tagging support Signed-off-by: trivernis --- src/endpoints/adding_tags.rs | 4 +- src/models/builders/mod.rs | 1 + src/models/builders/tagging_builder.rs | 70 ++++++++++++++++++++++++++ src/models/hydrus.rs | 6 +++ tests/wrapper/test_hydrus.rs | 19 ++++++- 5 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/models/builders/tagging_builder.rs diff --git a/src/endpoints/adding_tags.rs b/src/endpoints/adding_tags.rs index 15357b8..0a4c338 100644 --- a/src/endpoints/adding_tags.rs +++ b/src/endpoints/adding_tags.rs @@ -42,7 +42,7 @@ pub struct AddTagsRequestBuilder { } /// List of actions for a given tag -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialOrd, PartialEq, Hash)] pub enum TagAction { /// Add to a local tag service. AddToLocalService, @@ -63,6 +63,8 @@ pub enum TagAction { RescindPetitionFromRepository, } +impl Eq for TagAction {} + impl TagAction { fn into_id(self) -> u8 { match self { diff --git a/src/models/builders/mod.rs b/src/models/builders/mod.rs index 8645e63..2402223 100644 --- a/src/models/builders/mod.rs +++ b/src/models/builders/mod.rs @@ -1 +1,2 @@ pub mod import_builder; +pub mod tagging_builder; diff --git a/src/models/builders/tagging_builder.rs b/src/models/builders/tagging_builder.rs new file mode 100644 index 0000000..435d44c --- /dev/null +++ b/src/models/builders/tagging_builder.rs @@ -0,0 +1,70 @@ +use crate::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; +use crate::error::Result; +use crate::models::tag::Tag; +use crate::service::ServiceName; +use crate::Client; +use std::collections::HashMap; + +pub struct TaggingBuilder { + client: Client, + hashes: Vec, + tag_mappings: HashMap>>, +} + +impl TaggingBuilder { + pub(crate) fn new(client: Client) -> Self { + Self { + client, + hashes: Vec::new(), + tag_mappings: Default::default(), + } + } + + /// Adds a file that should get the tags defined for this request + pub fn add_file(mut self, hash: S) -> Self { + self.hashes.push(hash.to_string()); + + self + } + + /// Adds a single tag for a given service + pub fn add_tag(self, service: ServiceName, action: TagAction, tag: Tag) -> Self { + self.add_tags(service, action, vec![tag]) + } + + /// Adds tags with actions for the given service + pub fn add_tags(mut self, service: ServiceName, action: TagAction, mut tags: Vec) -> Self { + let service_action_mappings = + if let Some(service_action_mappings) = self.tag_mappings.get_mut(&service) { + service_action_mappings + } else { + self.tag_mappings.insert(service.clone(), HashMap::new()); + self.tag_mappings.get_mut(&service).unwrap() + }; + if let Some(action_tag_mappings) = service_action_mappings.get_mut(&action) { + action_tag_mappings.append(&mut tags) + } else { + service_action_mappings.insert(action, tags); + } + + self + } + + /// Executes the request + pub async fn run(self) -> Result<()> { + let mut request = AddTagsRequestBuilder::default().add_hashes(self.hashes); + for (service, action_tag_mappings) in self.tag_mappings { + for (action, tags) in action_tag_mappings { + for tag in tags { + request = request.add_tag_with_action( + service.0.clone(), + tag.to_string(), + action.clone(), + ); + } + } + } + + self.client.add_tags(request.build()).await + } +} diff --git a/src/models/hydrus.rs b/src/models/hydrus.rs index 038c136..004d5ea 100644 --- a/src/models/hydrus.rs +++ b/src/models/hydrus.rs @@ -1,4 +1,5 @@ use crate::builders::import_builder::ImportBuilder; +use crate::builders::tagging_builder::TaggingBuilder; use crate::endpoints::common::FileIdentifier; use crate::endpoints::searching_and_fetching_files::FileSearchLocation; use crate::error::Result; @@ -68,6 +69,11 @@ impl Hydrus { Ok(HydrusFile::from_metadata(self.client.clone(), metadata)) } + /// Starts a request to bulk add tags to files + pub fn tagging(&self) -> TaggingBuilder { + TaggingBuilder::new(self.client.clone()) + } + /// Searches for files that have the given tags and returns a list of hydrus files as a result pub async fn search( &self, diff --git a/tests/wrapper/test_hydrus.rs b/tests/wrapper/test_hydrus.rs index 7ce11c9..b919b87 100644 --- a/tests/wrapper/test_hydrus.rs +++ b/tests/wrapper/test_hydrus.rs @@ -1,6 +1,7 @@ use super::super::common; +use hydrus_api::endpoints::adding_tags::TagAction; use hydrus_api::endpoints::searching_and_fetching_files::FileSearchLocation; -use hydrus_api::service::ServiceType; +use hydrus_api::service::{ServiceName, ServiceType}; use hydrus_api::url::UrlType; #[tokio::test] @@ -43,3 +44,19 @@ async fn it_searches() { .await .unwrap(); } + +#[tokio::test] +async fn it_adds_tags() { + let hydrus = common::get_hydrus(); + hydrus + .tagging() + .add_tag( + ServiceName::my_tags(), + TagAction::AddToLocalService, + "summer".into(), + ) + .add_file("0000000000000000000000000000000000000000000000000000000000000000") + .run() + .await + .unwrap(); +} From deccbee806f568c99b5b2c6cc7feba467c3e71f5 Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 15 Jul 2021 20:38:33 +0200 Subject: [PATCH 11/12] Add retrieval of metadata to the file wrapper Signed-off-by: trivernis --- Cargo.toml | 1 + src/endpoints/common.rs | 2 +- src/error.rs | 2 + src/models/hydrus_file.rs | 119 ++++++++++++++++++++++++++++++------ tests/wrapper/test_files.rs | 8 +++ 5 files changed, 111 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2879b15..6e5d4de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ repository = "https://github.com/trivernis/hydrus-api-rs" serde = {version = "^1.0", features = ["derive"]} reqwest = {version = "0.11.4", features = ["json"]} log = "0.4.14" +mime = "0.3.16" [dev-dependencies] env_logger = "0.8.4" diff --git a/src/endpoints/common.rs b/src/endpoints/common.rs index ac9fb65..6160ee6 100644 --- a/src/endpoints/common.rs +++ b/src/endpoints/common.rs @@ -22,7 +22,7 @@ pub struct FileMetadataInfo { pub height: Option, pub duration: Option, pub has_audio: Option, - pub num_frames: Option, + pub num_frames: Option, pub num_words: Option, pub is_inbox: bool, pub is_local: bool, diff --git a/src/error.rs b/src/error.rs index c62f74e..4bc8549 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,6 +12,7 @@ pub enum Error { ImportVetoed(String), ImportFailed(String), FileNotFound(FileIdentifier), + InvalidMime(String), } impl fmt::Display for Error { @@ -25,6 +26,7 @@ impl fmt::Display for Error { Self::ImportFailed(msg) => write!(f, "File import failed: {}", msg), Self::ImportVetoed(msg) => write!(f, "File import vetoed: {}", msg), Self::FileNotFound(id) => write!(f, "File {:?} not found", id), + Self::InvalidMime(mime) => write!(f, "Failed to parse invalid mime {}", mime), } } } diff --git a/src/models/hydrus_file.rs b/src/models/hydrus_file.rs index 51abc5a..6c8267f 100644 --- a/src/models/hydrus_file.rs +++ b/src/models/hydrus_file.rs @@ -1,10 +1,11 @@ use crate::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; use crate::endpoints::common::{FileIdentifier, FileMetadataInfo, FileRecord}; -use crate::error::Result; +use crate::error::{Error, Result}; use crate::service::ServiceName; use crate::tag::Tag; use crate::utils::tag_list_to_string_list; use crate::Client; +use mime::Mime; use std::collections::HashMap; #[derive(Clone, Debug, PartialOrd, PartialEq)] @@ -77,25 +78,6 @@ impl HydrusFile { 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 { @@ -108,6 +90,84 @@ impl HydrusFile { } } + /// Returns the file size in bytes + pub async fn size(&mut self) -> Result> { + let metadata = self.metadata().await?; + + Ok(metadata.size.clone()) + } + + /// Returns the mime of the file + pub async fn mime(&mut self) -> Result { + let metadata = self.metadata().await?; + let mime = metadata + .mime + .as_str() + .parse() + .map_err(|_| Error::InvalidMime(metadata.mime.clone()))?; + + Ok(mime) + } + + /// Return the file extension + pub async fn ext(&mut self) -> Result { + let metadata = self.metadata().await?; + + Ok(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) { + Ok(Some((*width, *height))) + } else { + Ok(None) + } + } + + /// Returns the duration of the file in seconds if it's a video + pub async fn duration(&mut self) -> Result> { + let metadata = self.metadata().await?; + + Ok(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()) + } + + /// 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)) + } + + /// Returns if the file is currently in the inbox + pub async fn in_inbox(&mut self) -> Result { + let metadata = self.metadata().await?; + + Ok(metadata.is_inbox) + } + + /// Returns if the file is stored locally + pub async fn stored_locally(&mut self) -> Result { + let metadata = self.metadata().await?; + + Ok(metadata.is_local) + } + + /// Returns if the file has been moved to the trash + pub async fn moved_to_trashed(&mut self) -> Result { + let metadata = self.metadata().await?; + + Ok(metadata.is_trashed) + } + /// Associates the file with a list of urls pub async fn associate_urls(&mut self, urls: Vec) -> Result<()> { let hash = self.hash().await?; @@ -182,4 +242,23 @@ impl HydrusFile { pub async fn retrieve(&self) -> Result { self.client.get_file(self.id.clone()).await } + + /// 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()) + } } diff --git a/tests/wrapper/test_files.rs b/tests/wrapper/test_files.rs index 1fccbc4..2304681 100644 --- a/tests/wrapper/test_files.rs +++ b/tests/wrapper/test_files.rs @@ -80,3 +80,11 @@ async fn it_retrieves_content() { assert!(file.bytes.len() > 0) // assuming it exists } + +#[tokio::test] +async fn it_retrieves_metadata() { + let mut file = get_file().await; + assert!(file.dimensions().await.unwrap().is_some()); + assert!(file.stored_locally().await.unwrap()); + assert!(file.duration().await.unwrap().is_none()); +} From c746212e7d4df6070a9e37df20ae79232c2d9f89 Mon Sep 17 00:00:00 2001 From: trivernis Date: Thu, 15 Jul 2021 20:59:34 +0200 Subject: [PATCH 12/12] Update documentation Signed-off-by: trivernis --- Cargo.toml | 2 +- README.md | 48 ++++++++++++++++-- .../access_management.rs | 4 +- src/{endpoints => api_core}/adding_files.rs | 4 +- src/{endpoints => api_core}/adding_tags.rs | 2 +- src/{endpoints => api_core}/adding_urls.rs | 4 +- src/{ => api_core}/client.rs | 22 +++++---- src/{endpoints => api_core}/common.rs | 0 src/{endpoints => api_core}/mod.rs | 1 + .../searching_and_fetching_files.rs | 4 +- src/error.rs | 2 +- src/lib.rs | 49 +++++++++++++++---- src/utils.rs | 2 +- .../builders/import_builder.rs | 14 +++--- src/{models => wrapper}/builders/mod.rs | 0 .../builders/tagging_builder.rs | 6 +-- src/{models => wrapper}/hydrus.rs | 20 ++++---- src/{models => wrapper}/hydrus_file.rs | 8 +-- src/{models => wrapper}/mod.rs | 0 src/{models => wrapper}/page.rs | 0 src/{models => wrapper}/service.rs | 4 +- src/{models => wrapper}/tag.rs | 0 src/{models => wrapper}/url.rs | 6 +-- src/{models => wrapper}/version.rs | 0 tests/client/test_adding_tags.rs | 2 +- tests/client/test_adding_urls.rs | 2 +- .../test_searching_and_fetching_files.rs | 4 +- tests/common.rs | 3 +- tests/wrapper/test_files.rs | 8 +-- tests/wrapper/test_hydrus.rs | 8 +-- tests/wrapper/test_import.rs | 10 ++-- tests/wrapper/test_url.rs | 8 +-- 32 files changed, 161 insertions(+), 86 deletions(-) rename src/{endpoints => api_core}/access_management.rs (95%) rename src/{endpoints => api_core}/adding_files.rs (95%) rename src/{endpoints => api_core}/adding_tags.rs (99%) rename src/{endpoints => api_core}/adding_urls.rs (97%) rename src/{ => api_core}/client.rs (94%) rename src/{endpoints => api_core}/common.rs (100%) rename src/{endpoints => api_core}/mod.rs (95%) rename src/{endpoints => api_core}/searching_and_fetching_files.rs (93%) rename src/{models => wrapper}/builders/import_builder.rs (93%) rename src/{models => wrapper}/builders/mod.rs (100%) rename src/{models => wrapper}/builders/tagging_builder.rs (93%) rename src/{models => wrapper}/hydrus.rs (83%) rename src/{models => wrapper}/hydrus_file.rs (97%) rename src/{models => wrapper}/mod.rs (100%) rename src/{models => wrapper}/page.rs (100%) rename src/{models => wrapper}/service.rs (97%) rename src/{models => wrapper}/tag.rs (100%) rename src/{models => wrapper}/url.rs (93%) rename src/{models => wrapper}/version.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 6e5d4de..4d0d679 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydrus-api" -version = "0.2.0" +version = "0.3.0" authors = ["trivernis "] edition = "2018" license = "Apache-2.0" diff --git a/README.md b/README.md index 1f72a5b..3a88b9d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,51 @@ -# Hydrus Rust API +

+Hydrus Rust API +

+

+ + + + + + +

+ This is a WIP Rust Wrapper for the Hydrus Client API. The official API documentation can be found [here](https://hydrusnetwork.github.io/hydrus/help/client_api.html). -## Example +## Example with Wrapper + +```rust +use std::env; +use hydrus_api::api_core::searching_and_fetching_files::FileSearchLocation; +use hydrus_api::wrapper::tag::Tag; +use hydrus_api::wrapper::service::ServiceName; +use hydrus_api::wrapper::hydrus_file::FileStatus; +use hydrus_api::wrapper::page::PageIdentifier; + +#[tokio::main] +async fn main() { + let hydrus_url = env::var("HYDRUS_URL").unwrap(); + let access_key = env::var("HYDRUS_ACCESS_KEY").unwrap(); + + let hydrus = Hydrus::new(Client::new(hydrus_url, access_key)); + let files = hydrus.search(FileSearchLocation::Archive,vec![Tag::from("character:megumin")]).await.unwrap(); + + for mut file in files { + file.add_tags(ServiceName::my_tags(), vec![Tag::from("ark mage")]).await.unwrap(); + } + + let url = hydrus.import() + .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") + .page(PageIdentifier::name("My Import Page")) + .add_additional_tag(ServiceName::my_tags(), Tag::from("character:megumin")) + .show_page(true) + .run().await.unwrap(); +} +``` + +## Example with Client ```rust use hydrus_api::Client; @@ -15,7 +57,7 @@ async fn main() { Client::new( env::var("HYDRUS_URL").unwrap(), env::var("HYDRUS_ACCESS_KEY").unwrap(), - ).unwrap(); + ); // let's first import a file let hash = client.add_file("/path/to/my/file").await.unwrap().hash; diff --git a/src/endpoints/access_management.rs b/src/api_core/access_management.rs similarity index 95% rename from src/endpoints/access_management.rs rename to src/api_core/access_management.rs index d7c55ad..8c5ff11 100644 --- a/src/endpoints/access_management.rs +++ b/src/api_core/access_management.rs @@ -1,5 +1,5 @@ -use crate::endpoints::common::BasicServiceInfo; -use crate::endpoints::Endpoint; +use crate::api_core::common::BasicServiceInfo; +use crate::api_core::Endpoint; use std::collections::HashMap; pub static SERVICE_TYPE_LOCAL_TAGS: &str = "local_tags"; diff --git a/src/endpoints/adding_files.rs b/src/api_core/adding_files.rs similarity index 95% rename from src/endpoints/adding_files.rs rename to src/api_core/adding_files.rs index 412cf09..fce22b4 100644 --- a/src/endpoints/adding_files.rs +++ b/src/api_core/adding_files.rs @@ -1,5 +1,5 @@ -use crate::endpoints::common::BasicHashList; -use crate::endpoints::Endpoint; +use crate::api_core::common::BasicHashList; +use crate::api_core::Endpoint; pub static STATUS_IMPORT_SUCCESS: u8 = 1; pub static STATUS_IMPORT_ALREADY_EXISTS: u8 = 2; diff --git a/src/endpoints/adding_tags.rs b/src/api_core/adding_tags.rs similarity index 99% rename from src/endpoints/adding_tags.rs rename to src/api_core/adding_tags.rs index 0a4c338..91c883f 100644 --- a/src/endpoints/adding_tags.rs +++ b/src/api_core/adding_tags.rs @@ -1,4 +1,4 @@ -use crate::endpoints::Endpoint; +use crate::api_core::Endpoint; use std::collections::HashMap; #[derive(Debug, Clone, Deserialize)] diff --git a/src/endpoints/adding_urls.rs b/src/api_core/adding_urls.rs similarity index 97% rename from src/endpoints/adding_urls.rs rename to src/api_core/adding_urls.rs index 094262b..a92aabc 100644 --- a/src/endpoints/adding_urls.rs +++ b/src/api_core/adding_urls.rs @@ -1,4 +1,4 @@ -use crate::endpoints::Endpoint; +use crate::api_core::Endpoint; use serde::Serialize; use std::collections::HashMap; @@ -72,7 +72,7 @@ pub struct AddUrlRequest { /// /// Example: /// ``` -/// use hydrus_api::endpoints::adding_urls::AddUrlRequestBuilder; +/// use hydrus_api::api_core::adding_urls::AddUrlRequestBuilder; /// /// let request = AddUrlRequestBuilder::default() /// .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") diff --git a/src/client.rs b/src/api_core/client.rs similarity index 94% rename from src/client.rs rename to src/api_core/client.rs index 1e9f2c2..b714320 100644 --- a/src/client.rs +++ b/src/api_core/client.rs @@ -1,22 +1,22 @@ -use crate::endpoints::access_management::{ +use crate::api_core::access_management::{ ApiVersion, ApiVersionResponse, GetServices, GetServicesResponse, SessionKey, SessionKeyResponse, VerifyAccessKey, VerifyAccessKeyResponse, }; -use crate::endpoints::adding_files::{ +use crate::api_core::adding_files::{ AddFile, AddFileRequest, AddFileResponse, ArchiveFiles, ArchiveFilesRequest, DeleteFiles, DeleteFilesRequest, UnarchiveFiles, UnarchiveFilesRequest, UndeleteFiles, UndeleteFilesRequest, }; -use crate::endpoints::adding_tags::{AddTags, AddTagsRequest, CleanTags, CleanTagsResponse}; -use crate::endpoints::adding_urls::{ +use crate::api_core::adding_tags::{AddTags, AddTagsRequest, CleanTags, CleanTagsResponse}; +use crate::api_core::adding_urls::{ AddUrl, AddUrlRequest, AddUrlResponse, AssociateUrl, AssociateUrlRequest, GetUrlFiles, GetUrlFilesResponse, GetUrlInfo, GetUrlInfoResponse, }; -use crate::endpoints::common::{FileIdentifier, FileMetadataInfo, FileRecord}; -use crate::endpoints::searching_and_fetching_files::{ +use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord}; +use crate::api_core::searching_and_fetching_files::{ FileMetadata, FileMetadataResponse, FileSearchLocation, GetFile, SearchFiles, SearchFilesResponse, }; -use crate::endpoints::Endpoint; +use crate::api_core::Endpoint; use crate::error::{Error, Result}; use crate::utils::{number_list_to_json_array, string_list_to_json_array}; use reqwest::Response; @@ -26,6 +26,8 @@ use serde::Serialize; static ACCESS_KEY_HEADER: &str = "Hydrus-Client-API-Access-Key"; #[derive(Clone)] +/// A low level Client for the hydrus API. It provides basic abstraction +/// over the REST api. pub struct Client { inner: reqwest::Client, base_url: String, @@ -34,12 +36,12 @@ pub struct Client { impl Client { /// Creates a new client to start requests against the hydrus api. - pub fn new>(url: S, access_key: S) -> Result { - Ok(Self { + pub fn new>(url: S, access_key: S) -> Self { + Self { inner: reqwest::Client::new(), access_key: access_key.as_ref().to_string(), base_url: url.as_ref().to_string(), - }) + } } /// Starts a get request to the path diff --git a/src/endpoints/common.rs b/src/api_core/common.rs similarity index 100% rename from src/endpoints/common.rs rename to src/api_core/common.rs diff --git a/src/endpoints/mod.rs b/src/api_core/mod.rs similarity index 95% rename from src/endpoints/mod.rs rename to src/api_core/mod.rs index 468521c..222f522 100644 --- a/src/endpoints/mod.rs +++ b/src/api_core/mod.rs @@ -5,6 +5,7 @@ pub mod access_management; pub mod adding_files; pub mod adding_tags; pub mod adding_urls; +pub mod client; pub mod common; pub mod searching_and_fetching_files; diff --git a/src/endpoints/searching_and_fetching_files.rs b/src/api_core/searching_and_fetching_files.rs similarity index 93% rename from src/endpoints/searching_and_fetching_files.rs rename to src/api_core/searching_and_fetching_files.rs index 3a46d66..b3e20ef 100644 --- a/src/endpoints/searching_and_fetching_files.rs +++ b/src/api_core/searching_and_fetching_files.rs @@ -1,5 +1,5 @@ -use crate::endpoints::common::FileMetadataInfo; -use crate::endpoints::Endpoint; +use crate::api_core::common::FileMetadataInfo; +use crate::api_core::Endpoint; #[derive(Debug, Clone, Deserialize)] pub struct SearchFilesResponse { diff --git a/src/error.rs b/src/error.rs index 4bc8549..60f3ad6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use crate::endpoints::common::FileIdentifier; +use crate::api_core::common::FileIdentifier; use std::error::Error as StdError; use std::fmt; diff --git a/src/lib.rs b/src/lib.rs index 956c588..05af9c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,10 +3,41 @@ //! token that can be retrieved in the hydrus client from the *review services* dialog. //! Different actions require different permissions, you can read about it in the [official docs](https://hydrusnetwork.github.io/hydrus/help/client_api.html). //! -//! ## Usage Example +//! ## Hydrus Usage Example +//! +//! ``` +//! # use hydrus_api::{Hydrus, Client}; +//! use std::env; +//! use hydrus_api::api_core::searching_and_fetching_files::FileSearchLocation; +//! use hydrus_api::wrapper::tag::Tag; +//! use hydrus_api::wrapper::service::ServiceName; +//! use hydrus_api::wrapper::hydrus_file::FileStatus; +//! use hydrus_api::wrapper::page::PageIdentifier; +//! +//! # #[tokio::test] +//! # async fn doctest() { +//! let hydrus_url = env::var("HYDRUS_URL").unwrap(); +//! let access_key = env::var("HYDRUS_ACCESS_KEY").unwrap(); +//! let hydrus = Hydrus::new(Client::new(hydrus_url, access_key)); +//! let files = hydrus.search(FileSearchLocation::Archive,vec![Tag::from("character:megumin")]).await.unwrap(); +//! +//! for mut file in files { +//! file.add_tags(ServiceName::my_tags(), vec![Tag::from("ark mage")]).await.unwrap(); +//! } +//! +//! let url = hydrus.import() +//! .url("https://www.pixiv.net/member_illust.php?illust_id=83406361&mode=medium") +//! .page(PageIdentifier::name("My Import Page")) +//! .add_additional_tag(ServiceName::my_tags(), Tag::from("character:megumin")) +//! .show_page(true) +//! .run().await.unwrap(); +//! # } +//! ``` +//! +//! ## Client Usage Example //! ``` //! use hydrus_api::Client; -//! use hydrus_api::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; +//! use hydrus_api::api_core::adding_tags::{AddTagsRequestBuilder, TagAction}; //! use std::env; //! # #[tokio::test] //! # async fn doctest() { @@ -14,7 +45,7 @@ //! Client::new( //! env::var("HYDRUS_URL").unwrap(), //! env::var("HYDRUS_ACCESS_KEY").unwrap(), -//! ).unwrap(); +//! ); //! // let's first import a file //! let hash = client.add_file("/path/to/my/file").await.unwrap().hash; //! @@ -34,12 +65,10 @@ #[macro_use] extern crate serde; -pub mod client; -pub mod endpoints; +pub use api_core::client::Client; +pub use wrapper::hydrus::Hydrus; + +pub mod api_core; pub mod error; -mod models; pub(crate) mod utils; - -pub use client::Client; -pub use models::hydrus::Hydrus; -pub use models::*; +pub mod wrapper; diff --git a/src/utils.rs b/src/utils.rs index b06ba26..95f308a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -use crate::models::tag::Tag; +use crate::wrapper::tag::Tag; pub fn string_list_to_json_array(l: Vec) -> String { format!("[\"{}\"]", l.join("\",\"")) diff --git a/src/models/builders/import_builder.rs b/src/wrapper/builders/import_builder.rs similarity index 93% rename from src/models/builders/import_builder.rs rename to src/wrapper/builders/import_builder.rs index 48c5e4b..f9b9e6d 100644 --- a/src/models/builders/import_builder.rs +++ b/src/wrapper/builders/import_builder.rs @@ -1,12 +1,12 @@ -use crate::endpoints::adding_files::{STATUS_IMPORT_FAILED, STATUS_IMPORT_VETOED}; -use crate::endpoints::adding_urls::AddUrlRequestBuilder; +use crate::api_core::adding_files::{STATUS_IMPORT_FAILED, STATUS_IMPORT_VETOED}; +use crate::api_core::adding_urls::AddUrlRequestBuilder; use crate::error::{Error, Result}; -use crate::hydrus_file::HydrusFile; -use crate::models::url::Url; -use crate::page::PageIdentifier; -use crate::service::ServiceName; -use crate::tag::Tag; use crate::utils::tag_list_to_string_list; +use crate::wrapper::hydrus_file::HydrusFile; +use crate::wrapper::page::PageIdentifier; +use crate::wrapper::service::ServiceName; +use crate::wrapper::tag::Tag; +use crate::wrapper::url::Url; use crate::Client; use std::collections::HashMap; use std::io::Read; diff --git a/src/models/builders/mod.rs b/src/wrapper/builders/mod.rs similarity index 100% rename from src/models/builders/mod.rs rename to src/wrapper/builders/mod.rs diff --git a/src/models/builders/tagging_builder.rs b/src/wrapper/builders/tagging_builder.rs similarity index 93% rename from src/models/builders/tagging_builder.rs rename to src/wrapper/builders/tagging_builder.rs index 435d44c..c80fa8b 100644 --- a/src/models/builders/tagging_builder.rs +++ b/src/wrapper/builders/tagging_builder.rs @@ -1,7 +1,7 @@ -use crate::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; +use crate::api_core::adding_tags::{AddTagsRequestBuilder, TagAction}; use crate::error::Result; -use crate::models::tag::Tag; -use crate::service::ServiceName; +use crate::wrapper::service::ServiceName; +use crate::wrapper::tag::Tag; use crate::Client; use std::collections::HashMap; diff --git a/src/models/hydrus.rs b/src/wrapper/hydrus.rs similarity index 83% rename from src/models/hydrus.rs rename to src/wrapper/hydrus.rs index 004d5ea..0cf58ab 100644 --- a/src/models/hydrus.rs +++ b/src/wrapper/hydrus.rs @@ -1,16 +1,18 @@ -use crate::builders::import_builder::ImportBuilder; -use crate::builders::tagging_builder::TaggingBuilder; -use crate::endpoints::common::FileIdentifier; -use crate::endpoints::searching_and_fetching_files::FileSearchLocation; +use crate::api_core::common::FileIdentifier; +use crate::api_core::searching_and_fetching_files::FileSearchLocation; use crate::error::Result; -use crate::hydrus_file::HydrusFile; -use crate::models::url::Url; -use crate::models::version::Version; -use crate::service::Services; -use crate::tag::Tag; use crate::utils::tag_list_to_string_list; +use crate::wrapper::builders::import_builder::ImportBuilder; +use crate::wrapper::builders::tagging_builder::TaggingBuilder; +use crate::wrapper::hydrus_file::HydrusFile; +use crate::wrapper::service::Services; +use crate::wrapper::tag::Tag; +use crate::wrapper::url::Url; +use crate::wrapper::version::Version; use crate::Client; +/// A high level wrapper for the hydrus API for easier management of files, tags +/// urls etc. pub struct Hydrus { client: Client, } diff --git a/src/models/hydrus_file.rs b/src/wrapper/hydrus_file.rs similarity index 97% rename from src/models/hydrus_file.rs rename to src/wrapper/hydrus_file.rs index 6c8267f..12036cc 100644 --- a/src/models/hydrus_file.rs +++ b/src/wrapper/hydrus_file.rs @@ -1,9 +1,9 @@ -use crate::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; -use crate::endpoints::common::{FileIdentifier, FileMetadataInfo, FileRecord}; +use crate::api_core::adding_tags::{AddTagsRequestBuilder, TagAction}; +use crate::api_core::common::{FileIdentifier, FileMetadataInfo, FileRecord}; use crate::error::{Error, Result}; -use crate::service::ServiceName; -use crate::tag::Tag; use crate::utils::tag_list_to_string_list; +use crate::wrapper::service::ServiceName; +use crate::wrapper::tag::Tag; use crate::Client; use mime::Mime; use std::collections::HashMap; diff --git a/src/models/mod.rs b/src/wrapper/mod.rs similarity index 100% rename from src/models/mod.rs rename to src/wrapper/mod.rs diff --git a/src/models/page.rs b/src/wrapper/page.rs similarity index 100% rename from src/models/page.rs rename to src/wrapper/page.rs diff --git a/src/models/service.rs b/src/wrapper/service.rs similarity index 97% rename from src/models/service.rs rename to src/wrapper/service.rs index 447213c..1109616 100644 --- a/src/models/service.rs +++ b/src/wrapper/service.rs @@ -1,5 +1,5 @@ -use crate::endpoints::access_management::GetServicesResponse; -use crate::endpoints::access_management::{ +use crate::api_core::access_management::GetServicesResponse; +use crate::api_core::access_management::{ SERVICE_TYPE_ALL_KNOWN_FILES, SERVICE_TYPE_ALL_KNOWN_TAGS, SERVICE_TYPE_ALL_LOCAL_FILES, SERVICE_TYPE_FILE_REPOSITORIES, SERVICE_TYPE_LOCAL_FILES, SERVICE_TYPE_LOCAL_TAGS, SERVICE_TYPE_TAG_REPOSITORIES, SERVICE_TYPE_TRASH, diff --git a/src/models/tag.rs b/src/wrapper/tag.rs similarity index 100% rename from src/models/tag.rs rename to src/wrapper/tag.rs diff --git a/src/models/url.rs b/src/wrapper/url.rs similarity index 93% rename from src/models/url.rs rename to src/wrapper/url.rs index 5e124e3..bd82056 100644 --- a/src/models/url.rs +++ b/src/wrapper/url.rs @@ -1,9 +1,9 @@ -use crate::builders::import_builder::UrlImportBuilder; -use crate::endpoints::adding_urls::{ +use crate::api_core::adding_urls::{ URL_TYPE_FILE, URL_TYPE_GALLERY, URL_TYPE_POST, URL_TYPE_WATCHABLE, }; use crate::error::Result; -use crate::hydrus_file::HydrusFile; +use crate::wrapper::builders::import_builder::UrlImportBuilder; +use crate::wrapper::hydrus_file::HydrusFile; use crate::Client; #[derive(Clone, Debug, PartialOrd, PartialEq)] diff --git a/src/models/version.rs b/src/wrapper/version.rs similarity index 100% rename from src/models/version.rs rename to src/wrapper/version.rs diff --git a/tests/client/test_adding_tags.rs b/tests/client/test_adding_tags.rs index 2bc6fa3..2dacb13 100644 --- a/tests/client/test_adding_tags.rs +++ b/tests/client/test_adding_tags.rs @@ -1,5 +1,5 @@ use super::super::common; -use hydrus_api::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction}; +use hydrus_api::api_core::adding_tags::{AddTagsRequestBuilder, TagAction}; #[tokio::test] async fn it_cleans_tags() { diff --git a/tests/client/test_adding_urls.rs b/tests/client/test_adding_urls.rs index ab6dd3b..8b1f9aa 100644 --- a/tests/client/test_adding_urls.rs +++ b/tests/client/test_adding_urls.rs @@ -1,5 +1,5 @@ use super::super::common; -use hydrus_api::endpoints::adding_urls::{AddUrlRequestBuilder, URL_TYPE_POST}; +use hydrus_api::api_core::adding_urls::{AddUrlRequestBuilder, URL_TYPE_POST}; #[tokio::test] async fn it_returns_files_for_an_url() { diff --git a/tests/client/test_searching_and_fetching_files.rs b/tests/client/test_searching_and_fetching_files.rs index db0ffaf..5d4b146 100644 --- a/tests/client/test_searching_and_fetching_files.rs +++ b/tests/client/test_searching_and_fetching_files.rs @@ -1,6 +1,6 @@ use super::super::common; -use hydrus_api::endpoints::common::FileIdentifier; -use hydrus_api::endpoints::searching_and_fetching_files::FileSearchLocation; +use hydrus_api::api_core::common::FileIdentifier; +use hydrus_api::api_core::searching_and_fetching_files::FileSearchLocation; #[tokio::test] async fn is_searches_files() { diff --git a/tests/common.rs b/tests/common.rs index 48d41c4..dba8290 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,4 +1,4 @@ -use hydrus_api::client::Client; +use hydrus_api::api_core::client::Client; use hydrus_api::Hydrus; use log::LevelFilter; use std::env; @@ -21,7 +21,6 @@ pub fn get_client() -> Client { env::var("HYDRUS_URL").unwrap(), env::var("HYDRUS_ACCESS_KEY").unwrap(), ) - .unwrap() } pub fn get_hydrus() -> Hydrus { diff --git a/tests/wrapper/test_files.rs b/tests/wrapper/test_files.rs index 2304681..5291bfe 100644 --- a/tests/wrapper/test_files.rs +++ b/tests/wrapper/test_files.rs @@ -1,8 +1,8 @@ use super::super::common; -use hydrus_api::endpoints::adding_tags::TagAction; -use hydrus_api::endpoints::common::FileIdentifier; -use hydrus_api::hydrus_file::HydrusFile; -use hydrus_api::service::ServiceName; +use hydrus_api::api_core::adding_tags::TagAction; +use hydrus_api::api_core::common::FileIdentifier; +use hydrus_api::wrapper::hydrus_file::HydrusFile; +use hydrus_api::wrapper::service::ServiceName; async fn get_file() -> HydrusFile { let hydrus = common::get_hydrus(); diff --git a/tests/wrapper/test_hydrus.rs b/tests/wrapper/test_hydrus.rs index b919b87..794c347 100644 --- a/tests/wrapper/test_hydrus.rs +++ b/tests/wrapper/test_hydrus.rs @@ -1,8 +1,8 @@ use super::super::common; -use hydrus_api::endpoints::adding_tags::TagAction; -use hydrus_api::endpoints::searching_and_fetching_files::FileSearchLocation; -use hydrus_api::service::{ServiceName, ServiceType}; -use hydrus_api::url::UrlType; +use hydrus_api::api_core::adding_tags::TagAction; +use hydrus_api::api_core::searching_and_fetching_files::FileSearchLocation; +use hydrus_api::wrapper::service::{ServiceName, ServiceType}; +use hydrus_api::wrapper::url::UrlType; #[tokio::test] async fn it_retrieves_version_info() { diff --git a/tests/wrapper/test_import.rs b/tests/wrapper/test_import.rs index 39281c0..21a988e 100644 --- a/tests/wrapper/test_import.rs +++ b/tests/wrapper/test_import.rs @@ -1,9 +1,9 @@ use super::super::common; -use hydrus_api::builders::import_builder::FileImport; -use hydrus_api::page::PageIdentifier; -use hydrus_api::service::ServiceName; -use hydrus_api::tag::Tag; -use hydrus_api::url::UrlType; +use hydrus_api::wrapper::builders::import_builder::FileImport; +use hydrus_api::wrapper::page::PageIdentifier; +use hydrus_api::wrapper::service::ServiceName; +use hydrus_api::wrapper::tag::Tag; +use hydrus_api::wrapper::url::UrlType; #[tokio::test] async fn it_imports_file_paths() { diff --git a/tests/wrapper/test_url.rs b/tests/wrapper/test_url.rs index cc97bd6..700bad5 100644 --- a/tests/wrapper/test_url.rs +++ b/tests/wrapper/test_url.rs @@ -1,8 +1,8 @@ use super::super::common; -use hydrus_api::page::PageIdentifier; -use hydrus_api::service::ServiceName; -use hydrus_api::tag::Tag; -use hydrus_api::url::Url; +use hydrus_api::wrapper::page::PageIdentifier; +use hydrus_api::wrapper::service::ServiceName; +use hydrus_api::wrapper::tag::Tag; +use hydrus_api::wrapper::url::Url; async fn get_url() -> Url { let hydrus = common::get_hydrus();