Merge pull request #7 from Trivernis/develop

Develop
main
Julius Riegel 3 years ago committed by GitHub
commit ef09737c17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -23,6 +23,7 @@ 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;
use hydrus_api::wrapper::builders::search_builder::SortType;
use hydrus_api::wrapper::builders::tag_builder::{
SystemTagBuilder, Comparator
};
@ -33,11 +34,12 @@ async fn main() {
let access_key = env::var("HYDRUS_ACCESS_KEY").unwrap();
let hydrus = Hydrus::new(Client::new(hydrus_url, access_key));
let files = hydrus.search(vec![
Tag::from("character:megumin"),
SystemTagBuilder::new().archive().build(),
SystemTagBuilder::new().number_of_tags(Comparator::Greater, 12).build(),
]).await.unwrap();
let files = hydrus.search()
.add_tag(Tag::from("character:megumin"))
.add_tag(SystemTagBuilder::new().archive().build())
.add_tag(SystemTagBuilder::new().number_of_tags(Comparator::Greater, 12).build())
.sort(SortType::ModifiedTime)
.run().await.unwrap();
for mut file in files {
file.add_tags(ServiceName::my_tags(), vec![Tag::from("ark mage")]).await.unwrap();

@ -21,7 +21,8 @@ use crate::api_core::managing_pages::{
GetPages, GetPagesResponse,
};
use crate::api_core::searching_and_fetching_files::{
FileMetadata, FileMetadataResponse, GetFile, SearchFiles, SearchFilesResponse,
FileMetadata, FileMetadataResponse, FileSearchOptions, GetFile, SearchFiles,
SearchFilesResponse,
};
use crate::api_core::Endpoint;
use crate::error::{Error, Result};
@ -222,12 +223,15 @@ impl Client {
}
/// Searches for files in the inbox, the archive or both
pub async fn search_files(&self, tags: Vec<String>) -> Result<SearchFilesResponse> {
pub async fn search_files(
&self,
tags: Vec<String>,
options: FileSearchOptions,
) -> Result<SearchFilesResponse> {
log::trace!("Searching for files with tags {:?}", tags);
self.get_and_parse::<SearchFiles, [(&str, String)]>(&[(
"tags",
string_list_to_json_array(tags),
)])
let mut args = options.into_query_args();
args.push(("tags", string_list_to_json_array(tags)));
self.get_and_parse::<SearchFiles, [(&str, String)]>(&args)
.await
}

@ -10,6 +10,7 @@ pub mod common;
pub mod managing_cookies_and_http_headers;
pub mod managing_pages;
pub mod searching_and_fetching_files;
pub use searching_and_fetching_files::file_sort_type;
pub(crate) trait Endpoint {
type Request: Serialize;

@ -1,6 +1,101 @@
use crate::api_core::common::FileMetadataInfo;
use crate::api_core::Endpoint;
pub mod file_sort_type {
pub const SORT_FILE_SIZE: u8 = 0;
pub const SORT_FILE_DURATION: u8 = 1;
pub const SORT_FILE_IMPORT_TIME: u8 = 2;
pub const SORT_FILE_TYPE: u8 = 3;
pub const SORT_FILE_RANDOM: u8 = 4;
pub const SORT_FILE_WIDTH: u8 = 5;
pub const SORT_FILE_HEIGHT: u8 = 6;
pub const SORT_FILE_RATIO: u8 = 7;
pub const SORT_FILE_PIXEL_COUNT: u8 = 8;
pub const SORT_FILE_TAG_COUNT: u8 = 9;
pub const SORT_FILE_MEDIA_VIEWS: u8 = 10;
pub const SORT_FILE_MEDIA_VIEWTIME: u8 = 11;
pub const SORT_FILE_BITRATE: u8 = 12;
pub const SORT_FILE_HAS_AUDIO: u8 = 13;
pub const SORT_FILE_MODIFIED_TIME: u8 = 14;
pub const SORT_FILE_FRAMERATE: u8 = 15;
pub const SORT_FILE_FRAME_COUNT: u8 = 16;
}
#[derive(Clone, Debug, Default)]
pub struct FileSearchOptions {
file_service_name: Option<String>,
file_service_key: Option<String>,
tag_service_name: Option<String>,
tag_service_key: Option<String>,
file_sort_type: Option<u8>,
file_sort_asc: Option<bool>,
}
impl FileSearchOptions {
pub fn new() -> Self {
Self::default()
}
pub fn file_service_name<S: ToString>(mut self, name: S) -> Self {
self.file_service_name = Some(name.to_string());
self
}
pub fn file_service_key<S: ToString>(mut self, key: S) -> Self {
self.file_service_key = Some(key.to_string());
self
}
pub fn tag_service_name<S: ToString>(mut self, name: S) -> Self {
self.tag_service_name = Some(name.to_string());
self
}
pub fn tag_service_key<S: ToString>(mut self, key: S) -> Self {
self.tag_service_key = Some(key.to_string());
self
}
pub fn sort_type(mut self, sort_type: u8) -> Self {
self.file_sort_type = Some(sort_type);
self
}
pub fn asc(mut self) -> Self {
self.file_sort_asc = Some(true);
self
}
pub fn desc(mut self) -> Self {
self.file_sort_asc = Some(false);
self
}
pub(crate) fn into_query_args(self) -> Vec<(&'static str, String)> {
let mut args = Vec::new();
if let Some(sort) = self.file_sort_type {
args.push(("file_sort_type", sort.to_string()));
}
if let Some(file_service_name) = self.file_service_name {
args.push(("file_service_name", file_service_name));
}
if let Some(file_service_key) = self.file_service_key {
args.push(("file_service_key", file_service_key));
}
if let Some(tag_service_name) = self.tag_service_name {
args.push(("tag_service_name", tag_service_name))
}
if let Some(tag_service_key) = self.tag_service_key {
args.push(("tag_service_key", tag_service_key));
}
if let Some(sort_asc) = self.file_sort_asc {
args.push(("file_sort_asc", sort_asc.to_string()))
}
args
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct SearchFilesResponse {
pub file_ids: Vec<u64>,

@ -13,17 +13,20 @@
//! use hydrus_api::wrapper::hydrus_file::FileStatus;
//! use hydrus_api::wrapper::page::PageIdentifier;
//! use hydrus_api::wrapper::builders::tag_builder::{SystemTagBuilder, Comparator};
//! use hydrus_api::wrapper::builders::search_builder::SortType;
//!
//! # #[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(vec![
//! Tag::from("character:megumin"),
//! SystemTagBuilder::new().archive().build(),
//! SystemTagBuilder::new().tag_namespace_as_number("page", Comparator::Equal, 5).negate().build(),
//! ]).await.unwrap();
//! let files = hydrus.search()
//! .add_tag(Tag::from("character:megumin"))
//! .add_tag(SystemTagBuilder::new().archive().build())
//! .add_tag(SystemTagBuilder::new().tag_namespace_as_number("page", Comparator::Equal, 5).negate().build())
//! .sort_by(SortType::NumberOfPixels)
//! .sort_descending()
//! .run().await.unwrap();
//!
//! for mut file in files {
//! file.add_tags(ServiceName::my_tags(), vec![Tag::from("ark mage")]).await.unwrap();
@ -74,5 +77,5 @@ pub use wrapper::hydrus::Hydrus;
pub mod api_core;
pub mod error;
pub(crate) mod utils;
pub mod utils;
pub mod wrapper;

@ -1,11 +1,12 @@
use crate::api_core::common::FileIdentifier;
use crate::wrapper::tag::Tag;
use chrono::{Datelike, Duration};
pub fn string_list_to_json_array(l: Vec<String>) -> String {
pub(crate) fn string_list_to_json_array(l: Vec<String>) -> String {
format!("[\"{}\"]", l.join("\",\""))
}
pub fn number_list_to_json_array<T: ToString>(l: Vec<T>) -> String {
pub(crate) fn number_list_to_json_array<T: ToString>(l: Vec<T>) -> String {
format!(
"[{}]",
l.into_iter()
@ -23,7 +24,7 @@ pub fn tag_list_to_string_list(tags: Vec<Tag>) -> Vec<String> {
tags.into_iter().map(|t| t.to_string()).collect()
}
pub fn format_datetime<D: Datelike>(datetime: D) -> String {
pub(crate) fn format_datetime<D: Datelike>(datetime: D) -> String {
format!(
"{:04}-{:02}-{:02}",
datetime.year(),
@ -32,7 +33,7 @@ pub fn format_datetime<D: Datelike>(datetime: D) -> String {
)
}
pub fn format_duration(duration: Duration) -> String {
pub(crate) fn format_duration(duration: Duration) -> String {
let mut expression = String::new();
let days = duration.num_days();
let hours = duration.num_hours() % 24;
@ -56,3 +57,18 @@ pub fn format_duration(duration: Duration) -> String {
expression
}
pub(crate) fn split_file_identifiers_into_hashes_and_ids(
files: Vec<FileIdentifier>,
) -> (Vec<u64>, Vec<String>) {
let mut ids = Vec::new();
let mut hashes = Vec::new();
for file in files {
match file {
FileIdentifier::ID(id) => ids.push(id),
FileIdentifier::Hash(hash) => hashes.push(hash),
}
}
(ids, hashes)
}

@ -1,3 +1,4 @@
pub mod import_builder;
pub mod tagging_builder;
pub mod tag_builder;
pub mod search_builder;

@ -0,0 +1,115 @@
use crate::api_core::searching_and_fetching_files::FileSearchOptions;
use crate::error::Result;
use crate::utils::tag_list_to_string_list;
use crate::wrapper::hydrus_file::HydrusFile;
use crate::wrapper::service::ServiceName;
use crate::wrapper::tag::Tag;
use crate::Client;
pub enum SortType {
FileSize,
Duration,
ImportTime,
FileType,
Random,
Width,
Height,
Ratio,
NumberOfPixels,
NumberOfTags,
NumberOfMediaViewers,
MediaViewTime,
Bitrate,
HasAudio,
ModifiedTime,
Framerate,
NumberOfFrames,
}
#[derive(Clone, Debug)]
pub struct SearchBuilder {
client: Client,
tags: Vec<Tag>,
options: FileSearchOptions,
}
impl SearchBuilder {
pub(crate) fn new(client: Client) -> Self {
Self {
client,
tags: Vec::new(),
options: FileSearchOptions::new(),
}
}
/// Add multiple tags to filter by
pub fn add_tags(mut self, mut tags: Vec<Tag>) -> Self {
self.tags.append(&mut tags);
self
}
/// Add a tag to filter by
pub fn add_tag(mut self, tag: Tag) -> Self {
self.tags.push(tag);
self
}
/// Sets the sort type
pub fn sort_by(mut self, sort_type: SortType) -> Self {
self.options = self.options.sort_type(sort_type as u8);
self
}
/// Sorts descending
pub fn sort_descending(mut self) -> Self {
self.options = self.options.desc();
self
}
/// Sorts ascending
pub fn sort_ascending(mut self) -> Self {
self.options = self.options.asc();
self
}
/// Sets the file service name to search in
pub fn file_service_name(mut self, service: ServiceName) -> Self {
self.options = self.options.file_service_name(service);
self
}
/// Sets the tag service to search by
pub fn tag_service_name(mut self, service: ServiceName) -> Self {
self.options = self.options.tag_service_name(service);
self
}
/// Sets the file service key. This option is preferred over
/// setting it by name because it's faster
pub fn file_service_key<S: ToString>(mut self, key: S) -> Self {
self.options = self.options.file_service_key(key);
self
}
/// Sets the tag service key. This option is preferred over
/// setting it by name because it's faster
pub fn tag_service_key<S: ToString>(mut self, key: S) -> Self {
self.options = self.options.tag_service_key(key);
self
}
/// Runs the search
pub async fn run(self) -> Result<Vec<HydrusFile>> {
let client = self.client.clone();
let response = client
.search_files(tag_list_to_string_list(self.tags), self.options)
.await?;
let files = response
.file_ids
.into_iter()
.map(|id| HydrusFile::from_id(client.clone(), id))
.collect();
Ok(files)
}
}

@ -1,13 +1,12 @@
use crate::api_core::common::FileIdentifier;
use crate::error::Result;
use crate::utils::tag_list_to_string_list;
use crate::wrapper::address::Address;
use crate::wrapper::builders::import_builder::ImportBuilder;
use crate::wrapper::builders::search_builder::SearchBuilder;
use crate::wrapper::builders::tagging_builder::TaggingBuilder;
use crate::wrapper::hydrus_file::HydrusFile;
use crate::wrapper::page::HydrusPage;
use crate::wrapper::service::Services;
use crate::wrapper::tag::Tag;
use crate::wrapper::url::Url;
use crate::wrapper::version::Version;
use crate::Client;
@ -82,19 +81,9 @@ impl Hydrus {
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, tags: Vec<Tag>) -> Result<Vec<HydrusFile>> {
let search_result = self
.client
.search_files(tag_list_to_string_list(tags))
.await?;
let files = search_result
.file_ids
.into_iter()
.map(|id| HydrusFile::from_id(self.client.clone(), id))
.collect();
Ok(files)
/// Starts a request to search for files
pub fn search(&self) -> SearchBuilder {
SearchBuilder::new(self.client.clone())
}
/// Returns a hydrus page by page key

@ -18,6 +18,16 @@ pub enum FileStatus {
impl Eq for FileStatus {}
impl From<u8> for FileStatus {
fn from(v: u8) -> FileStatus {
match v {
3 => FileStatus::Deleted,
0 => FileStatus::ReadyForImport,
_ => FileStatus::InDatabase,
}
}
}
#[derive(Clone)]
pub struct HydrusFile {
pub(crate) client: Client,
@ -41,17 +51,10 @@ impl HydrusFile {
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,
status: status.into(),
metadata: None,
}
}

@ -1,5 +1,6 @@
use crate::api_core::common::{FileIdentifier, PageInformation};
use crate::error::Result;
use crate::utils::split_file_identifiers_into_hashes_and_ids;
use crate::Client;
#[derive(Clone)]
@ -40,27 +41,26 @@ impl HydrusPage {
/// Adds files to a page
pub async fn add_files(&self, files: Vec<FileIdentifier>) -> Result<()> {
let mut hashes = Vec::new();
let mut ids = Vec::new();
let (ids, mut hashes) = split_file_identifiers_into_hashes_and_ids(files);
for file in files {
match file {
FileIdentifier::ID(id) => ids.push(id),
FileIdentifier::Hash(hash) => hashes.push(hash),
}
}
// resolve file ids to hashes
if ids.len() > 0 && hashes.len() > 0 {
while let Some(id) = ids.pop() {
hashes.append(&mut self.resolve_file_ids_to_hashes(ids).await?);
self.client
.add_files_to_page(&self.key, [].to_vec(), hashes)
.await
}
async fn resolve_file_ids_to_hashes(&self, ids: Vec<u64>) -> Result<Vec<String>> {
let mut hashes = Vec::new();
for id in ids {
let metadata = self
.client
.get_file_metadata_by_identifier(FileIdentifier::ID(id))
.await?;
hashes.push(metadata.hash);
}
}
self.client.add_files_to_page(&self.key, ids, hashes).await
Ok(hashes)
}
}

@ -4,7 +4,9 @@ use crate::api_core::access_management::{
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::wrapper::builders::search_builder::SearchBuilder;
use crate::Client;
use std::collections::HashMap;
use std::convert::TryFrom;
@ -102,6 +104,22 @@ pub struct Service {
pub service_type: ServiceType,
}
impl Service {
pub fn search(&self) -> SearchBuilder {
let builder = SearchBuilder::new(self.client.clone());
match self.service_type {
ServiceType::LocalTags | ServiceType::TagRepositories | ServiceType::AllKnownTags => {
builder.tag_service_key(&self.key)
}
ServiceType::LocalFiles
| ServiceType::FileRepositories
| ServiceType::AllLocalFiles
| ServiceType::AllKnownFiles
| ServiceType::Trash => builder.file_service_key(&self.key),
}
}
}
#[derive(Clone)]
pub struct Services {
inner: HashMap<ServiceType, Vec<Service>>,
@ -140,13 +158,9 @@ impl Services {
/// 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
services.into_iter().collect()
} else {
Vec::with_capacity(0)
Vec::new()
}
}
}

@ -1,11 +1,17 @@
use super::super::common;
use hydrus_api::api_core::common::FileIdentifier;
use hydrus_api::api_core::file_sort_type::SORT_FILE_PIXEL_COUNT;
use hydrus_api::api_core::searching_and_fetching_files::FileSearchOptions;
#[tokio::test]
async fn is_searches_files() {
let client = common::get_client();
let options = FileSearchOptions::new()
.sort_type(SORT_FILE_PIXEL_COUNT)
.tag_service_name("public tag repository")
.file_service_name("all known files");
client
.search_files(vec!["beach".to_string()])
.search_files(vec!["beach".to_string()], options)
.await
.unwrap();
}

@ -1,7 +1,8 @@
mod test_address;
mod test_files;
mod test_hydrus;
mod test_import;
mod test_url;
mod test_page;
mod test_address;
mod test_service;
mod test_tags;
mod test_url;

@ -1,5 +1,6 @@
use super::super::common;
use hydrus_api::api_core::adding_tags::TagAction;
use hydrus_api::wrapper::builders::search_builder::SortType;
use hydrus_api::wrapper::service::{ServiceName, ServiceType};
use hydrus_api::wrapper::url::UrlType;
@ -36,7 +37,10 @@ async fn it_retrieves_url_information() {
async fn it_searches() {
let hydrus = common::get_hydrus();
hydrus
.search(vec!["character:megumin".into()])
.search()
.add_tag("character:megumin".into())
.sort_by(SortType::ModifiedTime)
.run()
.await
.unwrap();
}

@ -0,0 +1,27 @@
use super::super::common;
use hydrus_api::wrapper::service::{Service, ServiceType, Services};
async fn get_services() -> Services {
let hydrus = common::get_hydrus();
hydrus.services().await.unwrap()
}
async fn get_file_service() -> Service {
let services = get_services().await;
services
.get_services(ServiceType::LocalFiles)
.pop()
.unwrap()
.clone()
}
#[tokio::test]
async fn it_searches_for_files() {
let service = get_file_service().await;
service
.search()
.add_tag("character:rimuru tempest".into())
.run()
.await
.unwrap();
}

@ -10,7 +10,7 @@ use hydrus_api::wrapper::tag::Tag;
async fn retrieve_single_tag(tag: Tag) -> Result<()> {
let hydrus = common::get_hydrus();
hydrus.search(vec![tag]).await?;
hydrus.search().add_tag(tag).run().await?;
Ok(())
}

Loading…
Cancel
Save