Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions src/model/books.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,10 +588,49 @@ impl ModelController {
)
.await;
}
let lookup_query = RsLookupQuery::Book(RsLookupBook {
let raw_lookup_query = RsLookupQuery::Book(RsLookupBook {
name: None,
author: None,
ids: Some(book_ids),
ids: Some(book_ids.clone()),
page_key: None,
});
let raw_result = self
.serve_cached_entity_image(
library_id,
book_id,
raw_lookup_query,
&target_kind,
&config,
requesting_user,
)
.await;
if raw_result.is_ok() {
return raw_result;
}

let resolved_book = self
.get_book(library_id, book_id.to_string(), requesting_user)
.await;
let Ok(book) = resolved_book else {
return raw_result;
};

if book.item.id != book_id && !RsIds::is_id(&book.item.id) {
return self
.book_image(
library_id,
&book.item.id,
Some(target_kind),
size,
requesting_user,
)
.await;
}

let lookup_query = RsLookupQuery::Book(RsLookupBook {
name: Some(book.item.name.clone()),
author: None,
ids: Some(book.item.into()),
page_key: None,
});
self.serve_cached_entity_image(
Expand Down
14 changes: 12 additions & 2 deletions src/model/entity_images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ impl ModelController {
"Unable to get {} image url: {} kind {:?}",
config.cache_prefix, external_id, kind,
)))?;
let (_, mut writer) = local_provider.get_file_write_stream(&image_path).await?;
let image_reader = SourceRead::Request(image_request)
.into_reader(
Some(library_id),
Expand All @@ -151,7 +150,18 @@ impl ModelController {
false,
)
.await?;
writer.write_all(&resized).await?;
let (_, mut writer) = local_provider.get_file_write_stream(&image_path).await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the cache path returned by the writer

When concurrent requests miss the same cache entry, PathProvider::get_file_write_stream avoids overwriting by creating a suffixed file and returning its actual path, but this code discards that path. The second request therefore leaves an orphan on success; on failure, line 162 removes the first request's valid file while retaining its own partial suffixed file. Fresh evidence after the earlier cache-poisoning report is the collision behavior in src/plugins/sources/path_provider.rs:194-217; serialize creation or track the returned path for cleanup and publication.

Useful? React with 👍 / 👎.

let write_result = async {
writer.write_all(&resized).await?;
writer.shutdown().await
}
.await;
if let Err(error) = write_result {
// A failed write may leave a partial cache entry that would otherwise be
// treated as valid by the next request.
let _ = local_provider.remove(&image_path).await;
return Err(error.into());
}
}

let source = local_provider.get_file(&image_path, None).await?;
Expand Down
42 changes: 40 additions & 2 deletions src/model/movies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,9 +732,47 @@ impl ModelController {
)
.await;
}
let lookup_query = RsLookupQuery::Movie(RsLookupMovie {
let raw_lookup_query = RsLookupQuery::Movie(RsLookupMovie {
name: None,
ids: Some(movie_ids),
ids: Some(movie_ids.clone()),
page_key: None,
});
let raw_result = self
.serve_cached_entity_image(
library_id,
movie_id,
raw_lookup_query,
&kind,
&config,
requesting_user,
)
.await;
if raw_result.is_ok() {
return raw_result;
}

let resolved_movie = self
.get_movie(library_id, movie_id.to_string(), requesting_user)
.await;
let Ok(movie) = resolved_movie else {
return raw_result;
};

if movie.id != movie_id && !RsIds::is_id(&movie.id) {
return self
.movie_image(
library_id,
&movie.id,
Some(kind),
size,
requesting_user,
)
.await;
}

let lookup_query = RsLookupQuery::Movie(RsLookupMovie {
name: Some(movie.name.clone()),
ids: Some(movie.into()),
page_key: None,
});
self.serve_cached_entity_image(
Comment thread
neckaros marked this conversation as resolved.
Expand Down
123 changes: 68 additions & 55 deletions src/model/people.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use rand::seq::SliceRandom;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use strum_macros::EnumString;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
use tokio::io::{AsyncRead, AsyncReadExt};

use crate::{
domain::{
Expand All @@ -28,7 +28,7 @@ use crate::{
model::medias::MediaFileQuery,
plugins::sources::{error::SourcesError, AsyncReadPinBox, FileStreamResult, Source},
tools::{
image_tools::{convert_image_reader, resize_image_reader, ImageSize},
image_tools::{convert_image_reader, ImageSize},
log::log_info,
recognition::{BBox, DetectedFace, FaceRecognitionService},
video_tools::VideoTime,
Expand All @@ -46,6 +46,7 @@ use rs_plugin_common_interfaces::{
use tokio_util::io::StreamReader;

use super::{
entity_images::EntityImageConfig,
entity_search::optional_trakt_search,
error::{Error, Result},
users::ConnectedUser,
Expand Down Expand Up @@ -444,8 +445,13 @@ impl ModelController {
size: Option<ImageSize>,
requesting_user: &ConnectedUser,
) -> crate::Result<FileStreamResult<AsyncReadPinBox>> {
let target_kind = kind.clone().unwrap_or(ImageType::Poster);
let config = EntityImageConfig {
folder: ".portraits",
cache_prefix: "person",
};
if RsIds::is_id(person_id) {
let mut person_ids: RsIds = person_id.to_string().try_into()?;
let person_ids: RsIds = person_id.to_string().try_into()?;
let store = self.store.get_library_store_optional(library_id).ok_or(
SourcesError::UnableToFindPerson(
library_id.to_string(),
Expand All @@ -456,65 +462,67 @@ impl ModelController {
let existing_person = store.get_person_by_external_id(person_ids.clone()).await?;
if let Some(existing_person) = existing_person {
let image = self
.person_image(library_id, &existing_person.id, kind, size, requesting_user)
.person_image(
library_id,
&existing_person.id,
kind,
size,
requesting_user,
)
.await?;
Ok(image)
} else {
let target_kind = kind.unwrap_or(ImageType::Poster);
let local_provider = self.library_source_for_library(library_id).await?;
let image_path = format!(
"cache/person-{}-{}.avif",
person_id.replace(':', "-"),
target_kind
);
let raw_lookup_query = RsLookupQuery::Person(RsLookupPerson {
name: Some(String::new()),
ids: Some(person_ids.clone()),
page_key: None,
});
let raw_result = self
.serve_cached_entity_image(
library_id,
person_id,
raw_lookup_query,
&target_kind,
&config,
requesting_user,
)
.await;
if raw_result.is_ok() {
return raw_result;
}

if !local_provider.exists(&image_path).await {
let lookup_query = RsLookupPerson {
name: Some(String::new()),
ids: Some(person_ids.clone()),
page_key: None,
};
let image_request = self
.get_person_image_url(
lookup_query,
Some(library_id.to_string()),
&target_kind,
let resolved_person =
self.get_person(library_id, person_id.to_string(), requesting_user).await?;
let Some(person) = resolved_person else {
return raw_result;
};

if person.id != person_id && !RsIds::is_id(&person.id) {
return self
.person_image(
library_id,
&person.id,
kind,
size,
requesting_user,
)
.await?
.ok_or(crate::Error::NotFound(format!(
"Unable to get person image url: {:?} kind {:?}",
person_ids, target_kind
)))?;
let (_, mut writer) = local_provider.get_file_write_stream(&image_path).await?;
let image_reader = crate::plugins::sources::SourceRead::Request(image_request)
.into_reader(
Some(library_id),
None,
None,
Some((self.clone(), requesting_user)),
None,
)
.await?;
let resized = resize_image_reader(
image_reader.stream,
ImageSize::Large.to_size(),
image::ImageFormat::Avif,
Some(70),
false,
)
.await?;

writer.write_all(&resized).await?;
.await;
}

let source = local_provider.get_file(&image_path, None).await?;
match source {
crate::plugins::sources::SourceRead::Stream(s) => Ok(s),
crate::plugins::sources::SourceRead::Request(_) => {
Err(crate::Error::GenericRedseatError)
}
}
let lookup_query = RsLookupQuery::Person(RsLookupPerson {
name: Some(person.name.clone()),
ids: Some(person.into()),
page_key: None,
});
self.serve_cached_entity_image(
library_id,
person_id,
lookup_query,
&target_kind,
&config,
requesting_user,
)
.await
}
} else {
if !self
Expand All @@ -529,7 +537,12 @@ impl ModelController {
{
// Try to refresh from external source first
let refresh_result = self
.refresh_person_image(library_id, person_id, &kind, requesting_user)
.refresh_person_image(
library_id,
person_id,
&kind,
requesting_user,
)
.await;
match refresh_result {
Ok(_) => {
Expand Down
Loading