Compare commits

...

3 Commits

@ -41,6 +41,8 @@ use serde::Serialize;
use std::collections::HashMap;
use std::fmt::Debug;
use super::endpoints::adding_tags::{SearchTags, SearchTagsResponse, TagSearchOptions};
const ACCESS_KEY_HEADER: &str = "Hydrus-Client-API-Access-Key";
const CONTENT_TYPE_HEADER: &str = "Content-Type";
const ACCEPT_HEADER: &str = "Accept";
@ -196,6 +198,19 @@ impl Client {
Ok(())
}
/// Searches for tags by name
#[tracing::instrument(skip(self), level = "debug")]
pub async fn search_tags<S: ToString + Debug>(
&self,
query: S,
options: TagSearchOptions,
) -> Result<SearchTagsResponse> {
let mut args = options.into_query_args();
args.push(("search", query.to_string()));
self.get_and_parse::<SearchTags, [(&str, String)]>(&args)
.await
}
/// Searches for files
#[tracing::instrument(skip(self), level = "debug")]
pub async fn search_files(

@ -166,3 +166,91 @@ impl AddTagsRequestBuilder {
}
}
}
pub struct SearchTags;
impl Endpoint for SearchTags {
type Request = ();
type Response = SearchTagsResponse;
fn path() -> String {
String::from("add_tags/search_tags")
}
}
#[derive(Debug, Deserialize)]
pub struct SearchTagsResponse {
pub tags: Vec<TagWithCount>,
}
#[derive(Debug, Deserialize)]
pub struct TagWithCount {
/// The name of the tag
pub value: String,
/// The count of how many times it was found in the database
pub count: u64,
}
#[derive(Debug, Default)]
pub struct TagSearchOptions {
/// And optional filter for the service the tags should belong to
pub tag_service: Option<ServiceIdentifier>,
/// Controls how the tags in the result should be displayed
pub display_type: TagDisplayType,
}
#[derive(Debug)]
pub enum TagDisplayType {
/// Returns tags as stored in the hydrus database
Storage,
/// Returns tags as displayed by hydrus
Display,
}
impl Default for TagDisplayType {
fn default() -> Self {
Self::Storage
}
}
impl TagDisplayType {
fn to_api_string(&self) -> &'static str {
match self {
TagDisplayType::Storage => "storage",
TagDisplayType::Display => "display",
}
}
}
impl TagSearchOptions {
/// Sets the display type of the search result
pub fn display_type(mut self, display_type: TagDisplayType) -> Self {
self.display_type = display_type;
self
}
/// Adds a filter for the tag service that the tags we're searching for
/// should belong to.
pub fn tag_service(mut self, tag_service: ServiceIdentifier) -> Self {
self.tag_service = Some(tag_service);
self
}
pub(crate) fn into_query_args(self) -> Vec<(&'static str, String)> {
let mut args = Vec::new();
if let Some(service) = self.tag_service {
match service {
ServiceIdentifier::Name(name) => args.push(("tag_service_name", name)),
ServiceIdentifier::Key(key) => args.push(("tag_service_key", key)),
}
}
args.push((
"tag_display_type",
self.display_type.to_api_string().to_string(),
));
args
}
}

@ -207,11 +207,7 @@ pub struct FileFullMetadata {
pub is_trashed: bool,
pub file_services: FileMetadataServices,
pub known_urls: Vec<String>,
#[deprecated]
pub service_names_to_statuses_to_tags: HashMap<String, HashMap<String, Vec<String>>>,
pub service_keys_to_statuses_to_tags: HashMap<String, HashMap<String, Vec<String>>>,
#[deprecated]
pub service_names_to_statuses_to_display_tags: HashMap<String, HashMap<String, Vec<String>>>,
pub service_keys_to_statuses_to_display_tags: HashMap<String, HashMap<String, Vec<String>>>,
}

@ -4,4 +4,5 @@ pub mod notes_builder;
pub mod or_chain_builder;
pub mod search_builder;
pub mod tag_builder;
pub mod tag_search_builder;
pub mod tagging_builder;

@ -0,0 +1,52 @@
use crate::{
api_core::{
common::ServiceIdentifier,
endpoints::adding_tags::{TagDisplayType, TagSearchOptions, TagWithCount},
},
error::Result,
wrapper::tag::Tag,
Client,
};
pub struct TagSearchBuilder {
client: Client,
query: String,
options: TagSearchOptions,
}
impl TagSearchBuilder {
pub(crate) fn new(client: Client, query: String) -> Self {
Self {
client,
query,
options: TagSearchOptions::default(),
}
}
/// Returns a list of tags as displayed in hydrus
/// rather than what is stored in the database.
pub fn as_displayed(mut self) -> Self {
self.options = self.options.display_type(TagDisplayType::Display);
self
}
/// Adds an additioinal filter for the tag service
pub fn tag_service(mut self, tag_service: ServiceIdentifier) -> Self {
self.options = self.options.tag_service(tag_service);
self
}
/// Runs the search
pub async fn run(self) -> Result<Vec<(u64, Tag)>> {
let tags = self
.client
.search_tags(self.query, self.options)
.await?
.tags
.into_iter()
.map(|TagWithCount { value, count }| (count, Tag::from(value)))
.collect();
Ok(tags)
}
}

@ -14,6 +14,8 @@ use crate::wrapper::version::Version;
use crate::Client;
use std::fmt::Debug;
use super::builders::tag_search_builder::TagSearchBuilder;
/// A high level wrapper for the hydrus API for easier management of files, tags
/// urls etc.
pub struct Hydrus {
@ -94,6 +96,11 @@ impl Hydrus {
SearchBuilder::new(self.client.clone())
}
/// Starts a search request for tags with additional filter options
pub fn search_tags<S: ToString>(&self, query: S) -> TagSearchBuilder {
TagSearchBuilder::new(self.client.clone(), query.to_string())
}
/// Returns a hydrus page by page key
pub async fn page<S: AsRef<str> + Debug>(&self, page_key: S) -> Result<HydrusPage> {
let info_response = self.client.get_page_info(page_key).await?;

@ -7,7 +7,7 @@ use crate::error::{Error, Result};
use crate::utils::tag_list_to_string_list;
use crate::wrapper::builders::delete_files_builder::DeleteFilesBuilder;
use crate::wrapper::builders::notes_builder::AddNotesBuilder;
use crate::wrapper::service::ServiceName;
use crate::wrapper::tag::Tag;
use crate::Client;
use chrono::{NaiveDateTime, TimeZone, Utc};
@ -289,18 +289,19 @@ impl HydrusFile {
///
/// Deprecation: Use [HydrusFile::services_with_tags] instead.
#[deprecated(note = "Deprecated in the official API. Use services_with_tags instead.")]
pub async fn service_names_with_tags(&mut self) -> Result<HashMap<ServiceName, Vec<Tag>>> {
pub async fn service_names_with_tags(
&mut self,
) -> Result<HashMap<ServiceIdentifier, Vec<Tag>>> {
let metadata = self.metadata().await?;
let mut tag_mappings = HashMap::new();
#[allow(deprecated)]
for (service, status_tags) in &metadata.service_names_to_statuses_to_tags {
for (service, status_tags) in &metadata.service_keys_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);
tag_mappings.insert(ServiceIdentifier::Key(service.clone()), tag_list);
}
Ok(tag_mappings)

@ -1,7 +1,9 @@
use super::super::common;
use crate::common::test_data::EMPTY_HASH;
use hydrus_api::api_core::common::ServiceIdentifier;
use hydrus_api::api_core::endpoints::adding_tags::{AddTagsRequestBuilder, TagAction};
use hydrus_api::api_core::endpoints::adding_tags::{
AddTagsRequestBuilder, TagAction, TagDisplayType, TagSearchOptions,
};
#[tokio::test]
async fn it_cleans_tags() {
@ -36,3 +38,20 @@ async fn it_adds_tags() {
.build();
client.add_tags(request).await.unwrap();
}
/// This test requires that searching for "*" is permitted in hydrus
#[tokio::test]
async fn it_searches_for_tags() {
#![allow(deprecated)]
let client = common::get_client();
let response = client
.search_tags(
"*",
TagSearchOptions::default()
.display_type(TagDisplayType::Display)
.tag_service(ServiceIdentifier::name("public tag repository")),
)
.await
.unwrap();
assert!(response.tags.is_empty() == false)
}

@ -78,3 +78,9 @@ async fn it_sets_the_user_agent() {
.await
.unwrap();
}
#[tokio::test]
async fn it_searches_for_tags() {
let hydrus = common::get_hydrus();
hydrus.search_tags("t").as_displayed().run().await.unwrap();
}

Loading…
Cancel
Save