Add support for negated tag queries

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/4/head
trivernis 3 years ago
parent 7cf30cbfa1
commit b9bf5861b6

@ -841,7 +841,7 @@ dependencies = [
[[package]] [[package]]
name = "mediarepo-api" name = "mediarepo-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=af90986e88cc4ef7d797ecc9cfd0c306b2d4c7cd#af90986e88cc4ef7d797ecc9cfd0c306b2d4c7cd" source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=6cb2b0e467b3554b07d04f98d9244d8e4952db68#6cb2b0e467b3554b07d04f98d9244d8e4952db68"
dependencies = [ dependencies = [
"chrono", "chrono",
"serde", "serde",

@ -90,12 +90,23 @@ impl File {
#[tracing::instrument(level = "debug", skip(db))] #[tracing::instrument(level = "debug", skip(db))]
pub(crate) async fn find_by_tags( pub(crate) async fn find_by_tags(
db: DatabaseConnection, db: DatabaseConnection,
tag_ids: Vec<i64>, tag_ids: Vec<(i64, bool)>,
) -> RepoResult<Vec<Self>> { ) -> RepoResult<Vec<Self>> {
let mut condition = Condition::all(); let mut condition = Condition::all();
for tag in tag_ids { for (tag, negated) in tag_ids {
condition = condition.add( condition = if negated {
condition.add(
hash::Column::Id.not_in_subquery(
Query::select()
.expr(Expr::col(hash_tag::Column::HashId))
.from(hash_tag::Entity)
.cond_where(hash_tag::Column::TagId.eq(tag))
.to_owned(),
),
)
} else {
condition.add(
hash::Column::Id.in_subquery( hash::Column::Id.in_subquery(
Query::select() Query::select()
.expr(Expr::col(hash_tag::Column::HashId)) .expr(Expr::col(hash_tag::Column::HashId))
@ -103,7 +114,8 @@ impl File {
.cond_where(hash_tag::Column::TagId.eq(tag)) .cond_where(hash_tag::Column::TagId.eq(tag))
.to_owned(), .to_owned(),
), ),
); )
}
} }
let results: Vec<(hash::Model, Option<file::Model>)> = hash::Entity::find() let results: Vec<(hash::Model, Option<file::Model>)> = hash::Entity::find()
.find_also_related(file::Entity) .find_also_related(file::Entity)

@ -9,8 +9,10 @@ use mediarepo_core::image_processing::ThumbnailSize;
use mediarepo_core::utils::parse_namespace_and_tag; use mediarepo_core::utils::parse_namespace_and_tag;
use mediarepo_database::get_database; use mediarepo_database::get_database;
use sea_orm::DatabaseConnection; use sea_orm::DatabaseConnection;
use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::io::Cursor; use std::io::Cursor;
use std::iter::FromIterator;
use std::path::PathBuf; use std::path::PathBuf;
use tokio::fs::OpenOptions; use tokio::fs::OpenOptions;
use tokio::io::BufReader; use tokio::io::BufReader;
@ -97,9 +99,23 @@ impl Repo {
/// Finds all files by a list of tags /// Finds all files by a list of tags
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn find_files_by_tags(&self, tags: Vec<String>) -> RepoResult<Vec<File>> { pub async fn find_files_by_tags(&self, tags: Vec<(String, bool)>) -> RepoResult<Vec<File>> {
let tags = self.find_all_tags(tags).await?; let db_tags = self
let tag_ids = tags.into_iter().map(|tag| tag.id()).collect(); .find_all_tags(tags.iter().map(|t| t.0.clone()).collect())
.await?;
let tag_map: HashMap<String, bool> = HashMap::from_iter(tags.into_iter());
let tag_ids: Vec<(i64, bool)> = db_tags
.into_iter()
.map(|tag| {
(
tag.id(),
tag_map
.get(&tag.normalized_name())
.cloned()
.unwrap_or(false),
)
})
.collect();
File::find_by_tags(self.db.clone(), tag_ids).await File::find_by_tags(self.db.clone(), tag_ids).await
} }

@ -32,7 +32,7 @@ impl Tag {
#[tracing::instrument(level = "debug", skip(db))] #[tracing::instrument(level = "debug", skip(db))]
pub async fn all(db: DatabaseConnection) -> RepoResult<Vec<Self>> { pub async fn all(db: DatabaseConnection) -> RepoResult<Vec<Self>> {
let tags: Vec<Self> = tag::Entity::find() let tags: Vec<Self> = tag::Entity::find()
.inner_join(namespace::Entity) .left_join(namespace::Entity)
.select_also(namespace::Entity) .select_also(namespace::Entity)
.all(&db) .all(&db)
.await? .await?
@ -135,4 +135,13 @@ impl Tag {
.clone() .clone()
.map(|n| Namespace::new(self.db.clone(), n)) .map(|n| Namespace::new(self.db.clone(), n))
} }
/// Returns the normalized name of the tag (namespace:tag)
pub fn normalized_name(&self) -> String {
if let Some(namespace) = &self.namespace {
format!("{}:{}", namespace.name, self.model.name)
} else {
self.model.name.to_owned()
}
}
} }

@ -764,7 +764,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "mediarepo-api" name = "mediarepo-api"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=af90986e88cc4ef7d797ecc9cfd0c306b2d4c7cd#af90986e88cc4ef7d797ecc9cfd0c306b2d4c7cd" source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=6cb2b0e467b3554b07d04f98d9244d8e4952db68#6cb2b0e467b3554b07d04f98d9244d8e4952db68"
dependencies = [ dependencies = [
"chrono", "chrono",
"serde", "serde",

@ -29,4 +29,4 @@ features = ["tokio-executor"]
[dependencies.mediarepo-api] [dependencies.mediarepo-api]
git = "https://github.com/Trivernis/mediarepo-api.git" git = "https://github.com/Trivernis/mediarepo-api.git"
rev = "af90986e88cc4ef7d797ecc9cfd0c306b2d4c7cd" rev = "6cb2b0e467b3554b07d04f98d9244d8e4952db68"

@ -51,7 +51,7 @@ impl FilesNamespace {
async fn find_files(ctx: &Context, event: Event) -> IPCResult<()> { async fn find_files(ctx: &Context, event: Event) -> IPCResult<()> {
let tags = event.data::<FindFilesByTagsRequest>()?; let tags = event.data::<FindFilesByTagsRequest>()?;
let repo = get_repo_from_context(ctx).await; let repo = get_repo_from_context(ctx).await;
let tags = tags.tags.into_iter().map(|t| t.name).collect(); let tags = tags.tags.into_iter().map(|t| (t.name, t.negate)).collect();
let files = repo.find_files_by_tags(tags).await?; let files = repo.find_files_by_tags(tags).await?;
let responses: Vec<FileMetadataResponse> = files let responses: Vec<FileMetadataResponse> = files
.into_iter() .into_iter()

Loading…
Cancel
Save