Change implementation to store thumbnails independent from the database

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

@ -855,8 +855,8 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "mediarepo-api" name = "mediarepo-api"
version = "0.7.0" version = "0.10.0"
source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=3275c328fa6ac362f71fc548301b7c7c70725ca8#3275c328fa6ac362f71fc548301b7c7c70725ca8" source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85#3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85"
dependencies = [ dependencies = [
"chrono", "chrono",
"serde", "serde",
@ -871,6 +871,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"base64", "base64",
"futures 0.3.17", "futures 0.3.17",
"glob",
"itertools", "itertools",
"multibase", "multibase",
"multihash", "multihash",

@ -512,6 +512,12 @@ dependencies = [
"weezl", "weezl",
] ]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.11.2" version = "0.11.2"
@ -702,6 +708,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"base64", "base64",
"futures", "futures",
"glob",
"itertools", "itertools",
"multibase", "multibase",
"multihash", "multihash",

@ -17,6 +17,7 @@ typemap_rev = "0.1.5"
futures = "0.3.17" futures = "0.3.17"
thumbnailer = "0.1.0" thumbnailer = "0.1.0"
itertools = "0.10.1" itertools = "0.10.1"
glob = "0.3.0"
[dependencies.sea-orm] [dependencies.sea-orm]
version = "0.3.2" version = "0.3.2"

@ -0,0 +1,2 @@
pub mod file_hash_store;
pub mod thumbnail_store;

@ -0,0 +1,77 @@
use std::io::Result;
use std::path::PathBuf;
use tokio::fs;
use tokio::fs::OpenOptions;
use tokio::io::{AsyncWriteExt, BufWriter};
#[derive(Clone, Debug)]
pub struct ThumbnailStore {
path: PathBuf,
}
#[derive(Clone, Debug)]
pub struct Dimensions {
pub height: u32,
pub width: u32,
}
impl ThumbnailStore {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
/// Adds a thumbnail to be stored for a parent id
/// if the thumbnail already exists it will be recreated without warning
pub async fn add_thumbnail<S: ToString>(
&self,
parent_id: S,
size: Dimensions,
data: &[u8],
) -> Result<PathBuf> {
let parent_dir = self.path.join(parent_id.to_string());
let entry_path = parent_dir.join(format!("{}-{}", size.height, size.width));
if !parent_dir.exists() {
fs::create_dir_all(parent_dir).await?;
}
let file = OpenOptions::new()
.write(true)
.create(true)
.open(&entry_path)
.await?;
let mut writer = BufWriter::new(file);
writer.write_all(data).await?;
writer.flush().await?;
Ok(entry_path)
}
/// Returns all thumbnails for a parent id
pub async fn get_thumbnails<S: ToString>(
&self,
parent_id: S,
) -> Result<Vec<(Dimensions, PathBuf)>> {
let mut entries = Vec::new();
let parent_dir = self.path.join(parent_id.to_string());
if !parent_dir.exists() {
return Ok(vec![]);
}
let mut dir = fs::read_dir(parent_dir).await?;
while let Ok(Some(entry)) = dir.next_entry().await {
let file_name = entry.file_name();
let name = file_name.to_string_lossy();
let (height, width) = name
.split_once("-")
.and_then(|(height, width)| {
Some((height.parse::<u32>().ok()?, width.parse::<u32>().ok()?))
})
.unwrap_or((255, 255));
entries.push((Dimensions { height, width }, entry.path()))
}
Ok(entries)
}
}

@ -1,11 +1,11 @@
pub use futures;
pub use itertools;
pub use rmp_ipc;
pub use thumbnailer;
pub mod context; pub mod context;
pub mod error; pub mod error;
pub mod file_hash_store; pub mod fs;
pub mod settings; pub mod settings;
pub mod type_keys; pub mod type_keys;
pub mod utils; pub mod utils;
pub use futures;
pub use itertools;
pub use rmp_ipc;
pub use thumbnailer;

@ -18,7 +18,7 @@ impl Default for Settings {
port_range: (3400, 3500), port_range: (3400, 3500),
database_path: "./db/repo.db".to_string(), database_path: "./db/repo.db".to_string(),
default_file_store: "Main".to_string(), default_file_store: "Main".to_string(),
thumbnail_store: "Thumbnails".to_string(), thumbnail_store: "./thumbnails".to_string(),
} }
} }
} }

@ -569,6 +569,12 @@ dependencies = [
"weezl", "weezl",
] ]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.11.2" version = "0.11.2"
@ -770,6 +776,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"base64", "base64",
"futures", "futures",
"glob",
"itertools", "itertools",
"multibase", "multibase",
"multihash", "multihash",

@ -569,6 +569,12 @@ dependencies = [
"weezl", "weezl",
] ]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.11.2" version = "0.11.2"
@ -770,6 +776,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"base64", "base64",
"futures", "futures",
"glob",
"itertools", "itertools",
"multibase", "multibase",
"multihash", "multihash",

@ -20,7 +20,6 @@ use mediarepo_database::entities::tag;
use crate::file_type::FileType; use crate::file_type::FileType;
use crate::storage::Storage; use crate::storage::Storage;
use crate::tag::Tag; use crate::tag::Tag;
use crate::thumbnail::Thumbnail;
#[derive(Clone)] #[derive(Clone)]
pub struct File { pub struct File {
@ -225,12 +224,6 @@ impl File {
Ok(storage) Ok(storage)
} }
/// Returns a list of thumbnails for the file
#[tracing::instrument(level = "debug", skip(self))]
pub async fn thumbnails(&self) -> RepoResult<Vec<Thumbnail>> {
Thumbnail::for_file_id(self.db.clone(), self.model.id).await
}
/// Returns the list of tags of the file /// Returns the list of tags of the file
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn tags(&self) -> RepoResult<Vec<Tag>> { pub async fn tags(&self) -> RepoResult<Vec<Tag>> {

@ -1,9 +1,7 @@
use crate::file::File; use crate::file::File;
use crate::thumbnail::Thumbnail;
use mediarepo_core::error::RepoResult; use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::file; use mediarepo_database::entities::file;
use mediarepo_database::entities::hash; use mediarepo_database::entities::hash;
use mediarepo_database::entities::thumbnail;
use sea_orm::prelude::*; use sea_orm::prelude::*;
use sea_orm::{DatabaseConnection, Set}; use sea_orm::{DatabaseConnection, Set};
use std::fmt::Debug; use std::fmt::Debug;
@ -80,17 +78,4 @@ impl Hash {
Ok(file) Ok(file)
} }
/// Returns the the thumbnail associated with the hash
#[tracing::instrument(level = "debug", skip(self))]
pub async fn thumbnail(&self) -> RepoResult<Option<Thumbnail>> {
let thumbnail = self
.model
.find_related(thumbnail::Entity)
.one(&self.db)
.await?
.map(|thumb_model| Thumbnail::new(self.db.clone(), thumb_model, self.model.clone()));
Ok(thumbnail)
}
} }

@ -6,6 +6,7 @@ use crate::tag::Tag;
use crate::thumbnail::Thumbnail; use crate::thumbnail::Thumbnail;
use chrono::{Local, NaiveDateTime}; use chrono::{Local, NaiveDateTime};
use mediarepo_core::error::{RepoError, RepoResult}; use mediarepo_core::error::{RepoError, RepoResult};
use mediarepo_core::fs::thumbnail_store::{Dimensions, ThumbnailStore};
use mediarepo_core::itertools::Itertools; use mediarepo_core::itertools::Itertools;
use mediarepo_core::thumbnailer::ThumbnailSize; use mediarepo_core::thumbnailer::ThumbnailSize;
use mediarepo_core::utils::parse_namespace_and_tag; use mediarepo_core::utils::parse_namespace_and_tag;
@ -24,7 +25,7 @@ use tokio::io::BufReader;
pub struct Repo { pub struct Repo {
db: DatabaseConnection, db: DatabaseConnection,
main_storage: Option<Storage>, main_storage: Option<Storage>,
thumbnail_storage: Option<Storage>, thumbnail_storage: Option<ThumbnailStore>,
} }
impl Repo { impl Repo {
@ -65,15 +66,15 @@ impl Repo {
/// Sets the main storage /// Sets the main storage
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn set_main_storage<S: ToString + Debug>(&mut self, path: S) -> RepoResult<()> { pub async fn set_main_storage<S: ToString + Debug>(&mut self, name: S) -> RepoResult<()> {
self.main_storage = Storage::by_name(self.db.clone(), path.to_string()).await?; self.main_storage = Storage::by_name(self.db.clone(), name.to_string()).await?;
Ok(()) Ok(())
} }
/// Sets the default thumbnail storage /// Sets the default thumbnail storage
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn set_thumbnail_storage<S: ToString + Debug>(&mut self, path: S) -> RepoResult<()> { pub async fn set_thumbnail_storage(&mut self, path: PathBuf) -> RepoResult<()> {
self.thumbnail_storage = Storage::by_name(self.db.clone(), path.to_string()).await?; self.thumbnail_storage = Some(ThumbnailStore::new(path));
Ok(()) Ok(())
} }
@ -189,29 +190,41 @@ impl Repo {
.await .await
} }
/// Returns a thumbnail by its hash /// Returns all thumbnails of a file
#[tracing::instrument(level = "debug", skip(self))] pub async fn get_file_thumbnails(&self, file_hash: String) -> RepoResult<Vec<Thumbnail>> {
pub async fn thumbnail_by_hash<S: AsRef<str> + Debug>( let thumb_store = self.get_thumbnail_storage()?;
&self, let thumbnails = thumb_store
hash: S, .get_thumbnails(&file_hash)
) -> RepoResult<Option<Thumbnail>> { .await?
Thumbnail::by_hash(self.db.clone(), hash).await .into_iter()
.map(|(size, path)| Thumbnail {
file_hash: file_hash.to_owned(),
path,
size,
mime_type: mime::IMAGE_PNG.to_string(),
})
.collect_vec();
Ok(thumbnails)
} }
/// Creates thumbnails of all sizes for a file /// Creates thumbnails of all sizes for a file
#[tracing::instrument(level = "debug", skip(self, file))] #[tracing::instrument(level = "debug", skip(self, file))]
pub async fn create_thumbnails_for_file(&self, file: &File) -> RepoResult<()> { pub async fn create_thumbnails_for_file(&self, file: &File) -> RepoResult<Vec<Thumbnail>> {
let thumb_storage = self.get_thumbnail_storage()?; let thumb_storage = self.get_thumbnail_storage()?;
let size = ThumbnailSize::Medium; let size = ThumbnailSize::Medium;
let (height, width) = size.dimensions(); let (height, width) = size.dimensions();
let thumbs = file.create_thumbnail([size]).await?; let thumbs = file.create_thumbnail([size]).await?;
let mut created_thumbs = Vec::with_capacity(1);
for thumb in thumbs { for thumb in thumbs {
self.store_single_thumbnail(file, thumb_storage, height, width, thumb) let entry = self
.store_single_thumbnail(file.hash().to_owned(), thumb_storage, height, width, thumb)
.await?; .await?;
created_thumbs.push(entry);
} }
Ok(()) Ok(created_thumbs)
} }
#[tracing::instrument(level = "debug", skip(self, file))] #[tracing::instrument(level = "debug", skip(self, file))]
@ -228,34 +241,34 @@ impl Repo {
.pop() .pop()
.ok_or_else(|| RepoError::from("Failed to create thumbnail"))?; .ok_or_else(|| RepoError::from("Failed to create thumbnail"))?;
let thumbnail = self let thumbnail = self
.store_single_thumbnail(file, thumb_storage, height, width, thumb) .store_single_thumbnail(file.hash().to_owned(), thumb_storage, height, width, thumb)
.await?; .await?;
Ok(thumbnail) Ok(thumbnail)
} }
/// Stores a single thumbnail
async fn store_single_thumbnail( async fn store_single_thumbnail(
&self, &self,
file: &File, file_hash: String,
thumb_storage: &Storage, thumb_storage: &ThumbnailStore,
height: u32, height: u32,
width: u32, width: u32,
thumb: mediarepo_core::thumbnailer::Thumbnail, thumb: mediarepo_core::thumbnailer::Thumbnail,
) -> RepoResult<Thumbnail> { ) -> RepoResult<Thumbnail> {
let mut buf = Vec::new(); let mut buf = Vec::new();
thumb.write_png(&mut buf)?; thumb.write_png(&mut buf)?;
let hash = thumb_storage.store_entry(Cursor::new(buf)).await?; let size = Dimensions { height, width };
let path = thumb_storage
.add_thumbnail(&file_hash, size.clone(), &buf)
.await?;
let thumbnail = Thumbnail::add( let thumbnail = Thumbnail {
self.db.clone(), file_hash,
hash.id(), path,
file.id(), size,
thumb_storage.id(), mime_type: mime::IMAGE_PNG.to_string(),
height as i32, };
width as i32,
Some(mime::IMAGE_PNG.to_string()),
)
.await?;
Ok(thumbnail) Ok(thumbnail)
} }
@ -385,7 +398,7 @@ impl Repo {
} }
#[tracing::instrument(level = "trace", skip(self))] #[tracing::instrument(level = "trace", skip(self))]
fn get_thumbnail_storage(&self) -> RepoResult<&Storage> { fn get_thumbnail_storage(&self) -> RepoResult<&ThumbnailStore> {
if let Some(storage) = &self.thumbnail_storage { if let Some(storage) = &self.thumbnail_storage {
Ok(storage) Ok(storage)
} else { } else {

@ -1,6 +1,6 @@
use crate::hash::Hash; use crate::hash::Hash;
use mediarepo_core::error::RepoResult; use mediarepo_core::error::RepoResult;
use mediarepo_core::file_hash_store::FileHashStore; use mediarepo_core::fs::file_hash_store::FileHashStore;
use mediarepo_database::entities::storage; use mediarepo_database::entities::storage;
use mediarepo_database::entities::storage::ActiveModel as ActiveStorage; use mediarepo_database::entities::storage::ActiveModel as ActiveStorage;
use mediarepo_database::entities::storage::Model as StorageModel; use mediarepo_database::entities::storage::Model as StorageModel;

@ -1,140 +1,21 @@
use crate::storage::Storage;
use mediarepo_core::error::RepoResult; use mediarepo_core::error::RepoResult;
use mediarepo_database::entities::hash; use mediarepo_core::fs::thumbnail_store::Dimensions;
use mediarepo_database::entities::thumbnail; use std::path::PathBuf;
use sea_orm::prelude::*; use tokio::fs::{File, OpenOptions};
use sea_orm::{DatabaseConnection, Set};
use std::fmt::Debug;
use tokio::fs::File;
use tokio::io::BufReader; use tokio::io::BufReader;
pub struct Thumbnail { pub struct Thumbnail {
db: DatabaseConnection, pub file_hash: String,
model: thumbnail::Model, pub path: PathBuf,
hash: hash::Model, pub size: Dimensions,
pub mime_type: String,
} }
impl Thumbnail { impl Thumbnail {
#[tracing::instrument(level = "trace")]
pub(crate) fn new(db: DatabaseConnection, model: thumbnail::Model, hash: hash::Model) -> Self {
Self { db, model, hash }
}
/// Returns the thumbnail by id
#[tracing::instrument(level = "debug", skip(db))]
pub async fn by_id(db: DatabaseConnection, id: i64) -> RepoResult<Option<Self>> {
let model: Option<(thumbnail::Model, Option<hash::Model>)> =
thumbnail::Entity::find_by_id(id)
.find_also_related(hash::Entity)
.one(&db)
.await?;
if let Some((model, Some(hash))) = model {
Ok(Some(Self::new(db, model, hash)))
} else {
Ok(None)
}
}
/// Returns a thumbnail by hash
#[tracing::instrument(level = "debug", skip(db))]
pub async fn by_hash<S: AsRef<str> + Debug>(
db: DatabaseConnection,
hash: S,
) -> RepoResult<Option<Self>> {
let result: Option<(hash::Model, Option<thumbnail::Model>)> = hash::Entity::find()
.filter(hash::Column::Value.eq(hash.as_ref()))
.find_also_related(thumbnail::Entity)
.one(&db)
.await?;
if let Some((hash, Some(model))) = result {
Ok(Some(Self::new(db, model, hash)))
} else {
Ok(None)
}
}
/// Inserts a thumbnail into the database
#[tracing::instrument(level = "debug", skip(db))]
pub async fn add(
db: DatabaseConnection,
hash_id: i64,
file_id: i64,
storage_id: i64,
height: i32,
width: i32,
mime: Option<String>,
) -> RepoResult<Self> {
let active_model = thumbnail::ActiveModel {
storage_id: Set(storage_id),
hash_id: Set(hash_id),
file_id: Set(file_id),
height: Set(height),
width: Set(width),
mime: Set(mime),
..Default::default()
};
let active_model: thumbnail::ActiveModel = active_model.insert(&db).await?;
let thumbnail = Self::by_id(db, active_model.id.unwrap())
.await?
.expect("Inserted thumbnail does not exist");
Ok(thumbnail)
}
/// Returns all thumbnails for a given file
#[tracing::instrument(level = "debug", skip(db))]
pub async fn for_file_id(db: DatabaseConnection, file_id: i64) -> RepoResult<Vec<Self>> {
let thumb_models: Vec<(thumbnail::Model, Option<hash::Model>)> = thumbnail::Entity::find()
.filter(thumbnail::Column::FileId.eq(file_id))
.find_also_related(hash::Entity)
.all(&db)
.await?;
Ok(thumb_models
.into_iter()
.filter_map(|(m, h)| Some(Self::new(db.clone(), m, h?)))
.collect())
}
pub fn id(&self) -> i64 {
self.model.id
}
pub fn file_id(&self) -> i64 {
self.model.file_id
}
pub fn hash(&self) -> &String {
&self.hash.value
}
pub fn height(&self) -> i32 {
self.model.height
}
pub fn width(&self) -> i32 {
self.model.width
}
pub fn mime_type(&self) -> &Option<String> {
&self.model.mime
}
/// Returns the storage for the thumbnail
#[tracing::instrument(level = "debug", skip(self))]
pub async fn storage(&self) -> RepoResult<Storage> {
let storage = Storage::by_id(self.db.clone(), self.model.storage_id)
.await?
.expect("The FK storage_id doesn't exist?!");
Ok(storage)
}
/// Returns the reader of the thumbnail file /// Returns the reader of the thumbnail file
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn get_reader(&self) -> RepoResult<BufReader<File>> { pub async fn get_reader(&self) -> RepoResult<BufReader<File>> {
let storage = self.storage().await?; let file = OpenOptions::new().read(true).open(&self.path).await?;
storage.get_file_reader(self.hash()).await Ok(BufReader::new(file))
} }
} }

@ -599,6 +599,12 @@ dependencies = [
"weezl", "weezl",
] ]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.11.2" version = "0.11.2"
@ -796,8 +802,8 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "mediarepo-api" name = "mediarepo-api"
version = "0.7.0" version = "0.10.0"
source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=3275c328fa6ac362f71fc548301b7c7c70725ca8#3275c328fa6ac362f71fc548301b7c7c70725ca8" source = "git+https://github.com/Trivernis/mediarepo-api.git?rev=3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85#3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85"
dependencies = [ dependencies = [
"chrono", "chrono",
"serde", "serde",
@ -812,6 +818,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"base64", "base64",
"futures 0.3.17", "futures 0.3.17",
"glob",
"itertools", "itertools",
"multibase", "multibase",
"multihash", "multihash",

@ -34,4 +34,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 = "3275c328fa6ac362f71fc548301b7c7c70725ca8" rev = "3e8a415a4cf1c04e3e7ccefbcdd56b650ad40c85"

@ -37,12 +37,10 @@ impl FromModel<Tag> for TagResponse {
impl FromModel<Thumbnail> for ThumbnailMetadataResponse { impl FromModel<Thumbnail> for ThumbnailMetadataResponse {
fn from_model(model: Thumbnail) -> Self { fn from_model(model: Thumbnail) -> Self {
Self { Self {
id: model.id(), file_hash: model.file_hash,
file_id: model.file_id(), height: model.size.height,
hash: model.hash().to_owned(), width: model.size.width,
height: model.height(), mime_type: model.mime_type.to_owned(),
width: model.width(),
mime_type: model.mime_type().to_owned(),
} }
} }
} }

@ -1,5 +1,5 @@
use crate::from_model::FromModel; use crate::from_model::FromModel;
use crate::utils::{file_by_identifier, get_repo_from_context}; use crate::utils::{file_by_identifier, get_repo_from_context, hash_by_identifier};
use compare::Compare; use compare::Compare;
use mediarepo_api::types::files::{ use mediarepo_api::types::files::{
AddFileRequestHeader, FileMetadataResponse, FindFilesByTagsRequest, AddFileRequestHeader, FileMetadataResponse, FindFilesByTagsRequest,
@ -7,7 +7,7 @@ use mediarepo_api::types::files::{
SortKey, ThumbnailMetadataResponse, UpdateFileNameRequest, SortKey, ThumbnailMetadataResponse, UpdateFileNameRequest,
}; };
use mediarepo_api::types::identifier::FileIdentifier; use mediarepo_api::types::identifier::FileIdentifier;
use mediarepo_core::error::RepoError; use mediarepo_core::fs::thumbnail_store::Dimensions;
use mediarepo_core::itertools::Itertools; use mediarepo_core::itertools::Itertools;
use mediarepo_core::rmp_ipc::prelude::*; use mediarepo_core::rmp_ipc::prelude::*;
use mediarepo_core::thumbnailer::ThumbnailSize; use mediarepo_core::thumbnailer::ThumbnailSize;
@ -33,7 +33,6 @@ impl NamespaceProvider for FilesNamespace {
"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,
"read_thumbnail" => Self::read_thumbnail,
"get_thumbnail_of_size" => Self::get_thumbnail_of_size, "get_thumbnail_of_size" => Self::get_thumbnail_of_size,
"update_file_name" => Self::update_file_name "update_file_name" => Self::update_file_name
); );
@ -169,15 +168,15 @@ impl FilesNamespace {
async fn thumbnails<S: AsyncProtocolStream>(ctx: &Context<S>, event: Event) -> IPCResult<()> { async fn thumbnails<S: AsyncProtocolStream>(ctx: &Context<S>, event: Event) -> IPCResult<()> {
let request = event.data::<GetFileThumbnailsRequest>()?; let request = event.data::<GetFileThumbnailsRequest>()?;
let repo = get_repo_from_context(ctx).await; let repo = get_repo_from_context(ctx).await;
let file = file_by_identifier(request.id, &repo).await?; let file_hash = hash_by_identifier(request.id.clone(), &repo).await?;
let mut thumbnails = file.thumbnails().await?; let mut thumbnails = repo.get_file_thumbnails(file_hash).await?;
if thumbnails.len() == 0 { if thumbnails.is_empty() {
tracing::debug!("No thumbnails for file found. Creating thumbnails..."); tracing::debug!("No thumbnails for file found. Creating thumbnails...");
repo.create_thumbnails_for_file(&file).await?; let file = file_by_identifier(request.id, &repo).await?;
thumbnails = repo.create_thumbnails_for_file(&file).await?;
tracing::debug!("Thumbnails for file created."); tracing::debug!("Thumbnails for file created.");
} }
thumbnails = file.thumbnails().await?;
let thumb_responses: Vec<ThumbnailMetadataResponse> = thumbnails let thumb_responses: Vec<ThumbnailMetadataResponse> = thumbnails
.into_iter() .into_iter()
@ -190,33 +189,6 @@ impl FilesNamespace {
Ok(()) Ok(())
} }
/// Reads a thumbnail for the given thumbnail hash
#[tracing::instrument(skip_all)]
async fn read_thumbnail<S: AsyncProtocolStream>(
ctx: &Context<S>,
event: Event,
) -> IPCResult<()> {
let hash = event.data::<String>()?;
let repo = get_repo_from_context(ctx).await;
let thumbnail = repo
.thumbnail_by_hash(&hash)
.await?
.ok_or_else(|| RepoError::from("Thumbnail not found"))?;
let mut reader = thumbnail.get_reader().await?;
let mut buf = Vec::new();
reader.read_to_end(&mut buf).await?;
ctx.emitter
.emit_response_to(
event.id(),
Self::name(),
"read_thumbnail",
BytePayload::new(buf),
)
.await?;
Ok(())
}
/// Returns a thumbnail that is within the range of the requested sizes /// Returns a thumbnail that is within the range of the requested sizes
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn get_thumbnail_of_size<S: AsyncProtocolStream>( async fn get_thumbnail_of_size<S: AsyncProtocolStream>(
@ -225,14 +197,14 @@ impl FilesNamespace {
) -> IPCResult<()> { ) -> IPCResult<()> {
let request = event.data::<GetFileThumbnailOfSizeRequest>()?; let request = event.data::<GetFileThumbnailOfSizeRequest>()?;
let repo = get_repo_from_context(ctx).await; let repo = get_repo_from_context(ctx).await;
let file = file_by_identifier(request.id, &repo).await?; let file_hash = hash_by_identifier(request.id.clone(), &repo).await?;
let thumbnails = file.thumbnails().await?; let thumbnails = repo.get_file_thumbnails(file_hash).await?;
let min_size = request.min_size; let min_size = request.min_size;
let max_size = request.max_size; let max_size = request.max_size;
let found_thumbnail = thumbnails.into_iter().find(|thumb| { let found_thumbnail = thumbnails.into_iter().find(|thumb| {
let height = thumb.height() as u32; let Dimensions { height, width } = thumb.size;
let width = thumb.width() as u32;
height >= min_size.0 height >= min_size.0
&& height <= max_size.0 && height <= max_size.0
&& width >= min_size.1 && width >= min_size.1
@ -242,6 +214,7 @@ impl FilesNamespace {
let thumbnail = if let Some(thumbnail) = found_thumbnail { let thumbnail = if let Some(thumbnail) = found_thumbnail {
thumbnail thumbnail
} else { } else {
let file = file_by_identifier(request.id, &repo).await?;
let middle_size = ((max_size.0 + min_size.0) / 2, (max_size.1 + min_size.1) / 2); let middle_size = ((max_size.0 + min_size.0) / 2, (max_size.1 + min_size.1) / 2);
let thumbnail = repo let thumbnail = repo
.create_file_thumbnail(&file, ThumbnailSize::Custom(middle_size)) .create_file_thumbnail(&file, ThumbnailSize::Custom(middle_size))

@ -20,3 +20,16 @@ pub async fn file_by_identifier(identifier: FileIdentifier, repo: &Repo) -> Repo
}?; }?;
file.ok_or_else(|| RepoError::from("Thumbnail not found")) file.ok_or_else(|| RepoError::from("Thumbnail not found"))
} }
pub async fn hash_by_identifier(identifier: FileIdentifier, repo: &Repo) -> RepoResult<String> {
match identifier {
FileIdentifier::ID(id) => {
let file = repo
.file_by_id(id)
.await?
.ok_or_else(|| "Thumbnail not found")?;
Ok(file.hash().to_owned())
}
FileIdentifier::Hash(hash) => Ok(hash),
}
}

@ -113,7 +113,7 @@ async fn init_repo(opt: &Opt) -> RepoResult<(Settings, Repo)> {
let mut repo = get_repo(&opt.repo.join(&settings.database_path).to_str().unwrap()).await?; let mut repo = get_repo(&opt.repo.join(&settings.database_path).to_str().unwrap()).await?;
repo.set_main_storage(&settings.default_file_store).await?; repo.set_main_storage(&settings.default_file_store).await?;
repo.set_thumbnail_storage(&settings.thumbnail_store) repo.set_thumbnail_storage(opt.repo.join(&settings.thumbnail_store))
.await?; .await?;
Ok((settings, repo)) Ok((settings, repo))
} }
@ -285,7 +285,9 @@ async fn add_tags_from_tags_file(
#[tracing::instrument(skip(repo, file))] #[tracing::instrument(skip(repo, file))]
async fn create_file_thumbnails(repo: &Repo, file: File) -> RepoResult<()> { async fn create_file_thumbnails(repo: &Repo, file: File) -> RepoResult<()> {
if file.thumbnails().await?.len() == 0 { let file_thumbnails = repo.get_file_thumbnails(file.hash().to_owned()).await?;
if file_thumbnails.is_empty() {
repo.create_thumbnails_for_file(&file).await?; repo.create_thumbnails_for_file(&file).await?;
} }
Ok(()) Ok(())

Loading…
Cancel
Save