Add search function for files

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

@ -15,7 +15,7 @@ use mediarepo_database::entities::namespace;
use mediarepo_database::entities::tag; use mediarepo_database::entities::tag;
use mime::Mime; use mime::Mime;
use sea_orm::prelude::*; use sea_orm::prelude::*;
use sea_orm::{DatabaseConnection, Set}; use sea_orm::{Condition, DatabaseConnection, Set};
use sea_orm::{JoinType, QuerySelect}; use sea_orm::{JoinType, QuerySelect};
use tokio::io::BufReader; use tokio::io::BufReader;
@ -80,6 +80,28 @@ impl File {
} }
} }
pub(crate) async fn find_by_tags(
db: DatabaseConnection,
tag_ids: Vec<i64>,
) -> RepoResult<Vec<Self>> {
let mut condition = Condition::all();
for tag in tag_ids {
condition = condition.add(hash_tag::Column::TagId.eq(tag));
}
let results: Vec<(hash::Model, Option<file::Model>)> = hash::Entity::find()
.find_also_related(file::Entity)
.join(JoinType::Join, hash_tag::Relation::Hash.def())
.filter(condition)
.all(&db)
.await?;
let files: Vec<Self> = results
.into_iter()
.filter_map(|(hash, tag)| Some(Self::new(db.clone(), tag?, hash)))
.collect();
Ok(files)
}
/// Adds a file with its hash to the database /// Adds a file with its hash to the database
pub(crate) async fn add( pub(crate) async fn add(
db: DatabaseConnection, db: DatabaseConnection,

@ -82,6 +82,14 @@ impl Repo {
File::all(self.db.clone()).await File::all(self.db.clone()).await
} }
/// Finds all files by a list of tags
pub async fn find_files_by_tags(&self, tags: Vec<String>) -> RepoResult<Vec<File>> {
let tags = self.find_all_tags(tags).await?;
let tag_ids = tags.into_iter().map(|tag| tag.id()).collect();
File::find_by_tags(self.db.clone(), tag_ids).await
}
/// Adds a file to the database by its readable path in the file system /// Adds a file to the database by its readable path in the file system
pub async fn add_file_by_path(&self, path: PathBuf) -> RepoResult<File> { pub async fn add_file_by_path(&self, path: PathBuf) -> RepoResult<File> {
let mime_match = mime_guess::from_path(&path).first(); let mime_match = mime_guess::from_path(&path).first();
@ -141,6 +149,13 @@ impl Repo {
Tag::all(self.db.clone()).await Tag::all(self.db.clone()).await
} }
/// Finds all tags by name
pub async fn find_all_tags(&self, tags: Vec<String>) -> RepoResult<Vec<Tag>> {
let tags: Vec<(Option<String>, String)> =
tags.into_iter().map(parse_namespace_and_tag).collect();
Tag::all_by_name(self.db.clone(), tags).await
}
/// Adds or finds a tag /// Adds or finds a tag
pub async fn add_or_find_tag<S: ToString>(&self, tag: S) -> RepoResult<Tag> { pub async fn add_or_find_tag<S: ToString>(&self, tag: S) -> RepoResult<Tag> {
let (namespace, name) = parse_namespace_and_tag(tag.to_string()); let (namespace, name) = parse_namespace_and_tag(tag.to_string());
@ -153,7 +168,7 @@ impl Repo {
/// Adds or finds an unnamespaced tag /// Adds or finds an unnamespaced tag
pub async fn add_or_find_unnamespaced_tag(&self, name: String) -> RepoResult<Tag> { pub async fn add_or_find_unnamespaced_tag(&self, name: String) -> RepoResult<Tag> {
if let Some(tag) = Tag::by_name(self.db.clone(), &name).await? { if let Some(tag) = Tag::by_name(self.db.clone(), &name, None).await? {
Ok(tag) Ok(tag)
} else { } else {
self.add_unnamespaced_tag(name).await self.add_unnamespaced_tag(name).await
@ -171,7 +186,7 @@ impl Repo {
name: String, name: String,
namespace: String, namespace: String,
) -> RepoResult<Tag> { ) -> RepoResult<Tag> {
if let Some(tag) = Tag::by_name_and_namespace(self.db.clone(), &name, &namespace).await? { if let Some(tag) = Tag::by_name(self.db.clone(), &name, Some(namespace.clone())).await? {
Ok(tag) Ok(tag)
} else { } else {
self.add_namespaced_tag(name, namespace).await self.add_namespaced_tag(name, namespace).await

@ -3,7 +3,7 @@ use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::namespace; use mediarepo_database::entities::namespace;
use mediarepo_database::entities::tag; use mediarepo_database::entities::tag;
use sea_orm::prelude::*; use sea_orm::prelude::*;
use sea_orm::{DatabaseConnection, Set}; use sea_orm::{Condition, DatabaseConnection, Set};
#[derive(Clone)] #[derive(Clone)]
pub struct Tag { pub struct Tag {
@ -50,36 +50,45 @@ impl Tag {
Ok(tag) Ok(tag)
} }
/// Retrieves the unnamespaced tag by name /// Returns one tag by name and namespace
pub async fn by_name<S: AsRef<str>>( pub async fn by_name<S1: ToString>(
db: DatabaseConnection, db: DatabaseConnection,
name: S, name: S1,
namespace: Option<String>,
) -> RepoResult<Option<Self>> { ) -> RepoResult<Option<Self>> {
let tag = tag::Entity::find() let mut entries = Self::all_by_name(db, vec![(namespace, name.to_string())]).await?;
.filter(tag::Column::Name.eq(name.as_ref()))
.filter(tag::Column::NamespaceId.eq(Option::<i64>::None))
.one(&db)
.await?
.map(|t| Tag::new(db, t, None));
Ok(tag) Ok(entries.pop())
} }
/// Retrieves the namespaced tag by name and namespace /// Retrieves the namespaced tags by name and namespace
pub async fn by_name_and_namespace<S1: AsRef<str>, S2: AsRef<str>>( pub async fn all_by_name(
db: DatabaseConnection, db: DatabaseConnection,
name: S1, namespaces_with_names: Vec<(Option<String>, String)>,
namespace: S2, ) -> RepoResult<Vec<Self>> {
) -> RepoResult<Option<Self>> { let mut or_condition = Condition::any();
let tag = tag::Entity::find()
for (namespace, name) in namespaces_with_names {
let mut all_condition = Condition::all().add(tag::Column::Name.eq(name));
all_condition = if let Some(namespace) = namespace {
all_condition.add(namespace::Column::Name.eq(namespace))
} else {
all_condition.add(tag::Column::NamespaceId.eq(Option::<i64>::None))
};
or_condition = or_condition.add(all_condition);
}
let tags: Vec<Self> = tag::Entity::find()
.find_also_related(namespace::Entity) .find_also_related(namespace::Entity)
.filter(namespace::Column::Name.eq(namespace.as_ref())) .filter(or_condition)
.filter(tag::Column::Name.eq(name.as_ref())) .all(&db)
.one(&db)
.await? .await?
.map(|(t, n)| Self::new(db.clone(), t, n)); .into_iter()
.map(|(t, n)| Self::new(db.clone(), t, n))
.collect();
Ok(tag) Ok(tags)
} }
/// Adds a new tag to the database /// Adds a new tag to the database

@ -1,4 +1,6 @@
use crate::types::requests::{AddFileRequest, GetFileThumbnailsRequest, ReadFileRequest}; use crate::types::requests::{
AddFileRequest, FindFilesByTagsRequest, GetFileThumbnailsRequest, ReadFileRequest,
};
use crate::types::responses::{FileResponse, ThumbnailResponse}; use crate::types::responses::{FileResponse, ThumbnailResponse};
use crate::utils::{file_by_identifier, get_repo_from_context}; use crate::utils::{file_by_identifier, get_repo_from_context};
use mediarepo_core::error::RepoError; use mediarepo_core::error::RepoError;
@ -16,6 +18,7 @@ impl NamespaceProvider for FilesNamespace {
fn register(handler: &mut EventHandler) { fn register(handler: &mut EventHandler) {
events!(handler, events!(handler,
"all_files" => Self::all_files, "all_files" => Self::all_files,
"find_files" => Self::find_files,
"add_file" => Self::add_file, "add_file" => Self::add_file,
"read_file" => Self::read_file, "read_file" => Self::read_file,
"get_thumbnails" => Self::thumbnails, "get_thumbnails" => Self::thumbnails,
@ -38,6 +41,18 @@ impl FilesNamespace {
Ok(()) Ok(())
} }
/// Searches for files by tags
async fn find_files(ctx: &Context, event: Event) -> IPCResult<()> {
let tags = event.data::<FindFilesByTagsRequest>()?;
let repo = get_repo_from_context(ctx).await;
let files = repo.find_files_by_tags(tags.tags).await?;
let responses: Vec<FileResponse> = files.into_iter().map(FileResponse::from).collect();
ctx.emitter
.emit_response_to(event.id(), Self::name(), "find_files", responses)
.await?;
Ok(())
}
/// Adds a file to the repository /// Adds a file to the repository
async fn add_file(ctx: &Context, event: Event) -> IPCResult<()> { async fn add_file(ctx: &Context, event: Event) -> IPCResult<()> {
let request = event.data::<AddFileRequest>()?; let request = event.data::<AddFileRequest>()?;
@ -68,7 +83,7 @@ impl FilesNamespace {
reader.read_to_end(&mut buf).await?; reader.read_to_end(&mut buf).await?;
ctx.emitter ctx.emitter
.emit_response_to(event.id(), Self::name(), "read_file", buf) .emit_response_to(event.id(), Self::name(), "read_file", BytePayload::new(buf))
.await?; .await?;
Ok(()) Ok(())
@ -104,7 +119,12 @@ impl FilesNamespace {
let mut buf = Vec::new(); let mut buf = Vec::new();
reader.read_to_end(&mut buf).await?; reader.read_to_end(&mut buf).await?;
ctx.emitter ctx.emitter
.emit_response_to(event.id(), Self::name(), "read_thumbnail", buf) .emit_response_to(
event.id(),
Self::name(),
"read_thumbnail",
BytePayload::new(buf),
)
.await?; .await?;
Ok(()) Ok(())

@ -14,3 +14,8 @@ pub enum FileIdentifier {
pub type ReadFileRequest = FileIdentifier; pub type ReadFileRequest = FileIdentifier;
pub type GetFileThumbnailsRequest = FileIdentifier; pub type GetFileThumbnailsRequest = FileIdentifier;
pub type GetFileTagsRequest = FileIdentifier; pub type GetFileTagsRequest = FileIdentifier;
#[derive(Serialize, Deserialize)]
pub struct FindFilesByTagsRequest {
pub tags: Vec<String>,
}

Loading…
Cancel
Save