Skip to content

Commit

Permalink
Merge branch 'develop' into al/fix-emailer-form
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronleopold authored Sep 1, 2024
2 parents 5bd8fd8 + 38ffe05 commit d574b99
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 160 deletions.
91 changes: 37 additions & 54 deletions apps/server/src/routers/api/v1/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use stump_core::{
db::{
entity::{
library_series_ids_media_ids_include, library_thumbnails_deletion_include,
FileStatus, Library, LibraryOptions, LibraryScanMode, LibraryStats, Media,
Series, Tag, User, UserPermission,
macros::series_or_library_thumbnail, FileStatus, Library, LibraryOptions,
LibraryScanMode, LibraryStats, Media, Series, Tag, User, UserPermission,
},
query::pagination::{Pageable, Pagination, PaginationQuery},
PrismaCountTrait,
Expand Down Expand Up @@ -565,37 +565,35 @@ async fn get_library_media(
}

pub(crate) fn get_library_thumbnail(
library: &library::Data,
first_series: &series::Data,
first_book: &media::Data,
id: &str,
first_series: series_or_library_thumbnail::Data,
first_book: Option<series_or_library_thumbnail::media::Data>,
image_format: Option<ImageFormat>,
config: &StumpConfig,
) -> APIResult<(ContentType, Vec<u8>)> {
let library_id = library.id.clone();

if let Some(format) = image_format.clone() {
let extension = format.extension();

let path = config
.get_thumbnails_dir()
.join(format!("{}.{}", library_id, extension));
.join(format!("{}.{}", id, extension));

if path.exists() {
tracing::trace!(?path, library_id, "Found generated library thumbnail");
tracing::trace!(?path, id, "Found generated library thumbnail");
return Ok((ContentType::from(format), read_entire_file(path)?));
}
}

if let Some(path) = get_unknown_thumnail(&library_id, config.get_thumbnails_dir()) {
tracing::debug!(path = ?path, library_id, "Found library thumbnail that does not align with config");
if let Some(path) = get_unknown_thumnail(id, config.get_thumbnails_dir()) {
tracing::debug!(path = ?path, id, "Found library thumbnail that does not align with config");
let FileParts { extension, .. } = path.file_parts();
return Ok((
ContentType::from_extension(extension.as_str()),
read_entire_file(path)?,
));
}

get_series_thumbnail(first_series, first_book, image_format, config)
get_series_thumbnail(&first_series.id, first_book, image_format, config)
}

// TODO: ImageResponse for utoipa
Expand Down Expand Up @@ -624,55 +622,40 @@ async fn get_library_thumbnail_handler(
let user = get_session_user(&session)?;
let age_restriction = user.age_restriction.as_ref();

let series_filters = chain_optional_iter(
[
series::library_id::equals(Some(id.clone())),
series::library::is(vec![library_not_hidden_from_user_filter(&user)]),
],
[age_restriction
.map(|ar| apply_series_age_restriction(ar.age, ar.restrict_on_unset))],
);
let book_filters = chain_optional_iter(
[],
[age_restriction
.as_ref()
.map(|ar| apply_media_age_restriction(ar.age, ar.restrict_on_unset))],
);

let first_series = db
.series()
// Find the first series in the library which satisfies the age restriction
.find_first(chain_optional_iter(
[
series::library_id::equals(Some(id.clone())),
series::library::is(vec![library_not_hidden_from_user_filter(&user)]),
],
[age_restriction
.map(|ar| apply_series_age_restriction(ar.age, ar.restrict_on_unset))],
))
.with(
// Then load the first media in that series which satisfies the age restriction
series::media::fetch(chain_optional_iter(
[],
[age_restriction
.as_ref()
.map(|ar| apply_media_age_restriction(ar.age, ar.restrict_on_unset))],
))
.take(1)
.order_by(media::name::order(Direction::Asc)),
)
.with(series::library::fetch().with(library::library_options::fetch()))
.find_first(series_filters)
.order_by(series::name::order(Direction::Asc))
.select(series_or_library_thumbnail::select(book_filters))
.exec()
.await?
.ok_or(APIError::NotFound("Library has no series".to_string()))?;
let first_book = first_series.media.first().cloned();

let library = first_series
.library()?
.ok_or(APIError::Unknown(String::from("Failed to load library")))?;
let image_format = library
.library_options()
.map(LibraryOptions::from)?
.thumbnail_config
.map(|config| config.format);

let first_book = first_series.media()?.first().ok_or(APIError::NotFound(
"Library has no media to get thumbnail from".to_string(),
))?;

get_library_thumbnail(
library,
&first_series,
first_book,
image_format,
&ctx.config,
)
.map(ImageResponse::from)
let library_options = first_series
.library
.as_ref()
.map(|l| l.library_options.clone())
.map(LibraryOptions::from);
let image_format = library_options.and_then(|o| o.thumbnail_config.map(|c| c.format));

get_library_thumbnail(&id, first_series, first_book, image_format, &ctx.config)
.map(ImageResponse::from)
}

#[derive(Deserialize, ToSchema, specta::Type)]
Expand Down
75 changes: 25 additions & 50 deletions apps/server/src/routers/api/v1/media.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use stump_core::{
db::{
entity::{
macros::{
finished_reading_session_with_book_pages, reading_session_with_book_pages,
finished_reading_session_with_book_pages, media_thumbnail,
reading_session_with_book_pages,
},
ActiveReadingSession, FinishedReadingSession, LibraryOptions, Media,
PageDimension, PageDimensionsEntity, ProgressUpdateReturn, User,
Expand All @@ -40,7 +41,7 @@ use stump_core::{
read_entire_file, ContentType, FileParts, PathUtils,
},
prisma::{
active_reading_session, finished_reading_session, library, library_options,
active_reading_session, finished_reading_session, library,
media::{self, OrderByParam as MediaOrderByParam, WhereParam},
media_metadata, series, series_metadata, tag, user, PrismaClient,
},
Expand Down Expand Up @@ -953,8 +954,6 @@ async fn get_media_page(
}
}

// TODO: Refactor this transaction. I must have been very tired when I wrote it lol
// No thoughts, head empty
pub(crate) async fn get_media_thumbnail_by_id(
id: String,
db: &PrismaClient,
Expand All @@ -974,78 +973,54 @@ pub(crate) async fn get_media_thumbnail_by_id(
[age_restrictions],
);

let result = db
._transaction()
.run(|client| async move {
let book = client
.media()
.find_first(where_params)
.order_by(media::name::order(Direction::Asc))
.with(media::series::fetch())
.exec()
.await?;
let book = db
.media()
.find_first(where_params)
.select(media_thumbnail::select())
.exec()
.await?
.ok_or_else(|| APIError::NotFound("Book not found".to_string()))?;

if let Some(book) = book {
let library_id = match book.series() {
Ok(Some(series)) => Some(series.library_id.clone()),
_ => None,
}
.flatten();
let library_options = book
.series
.and_then(|s| s.library.map(|l| l.library_options))
.map(LibraryOptions::from);
let image_format = library_options.and_then(|o| o.thumbnail_config.map(|c| c.format));

client
.library_options()
.find_first(vec![library_options::library_id::equals(library_id)])
.exec()
.await
.map(|options| (Some(book), options.map(LibraryOptions::from)))
} else {
Ok((None, None))
}
})
.await?;
tracing::trace!(?result, "get_media_thumbnail transaction completed");

match result {
(Some(book), Some(options)) => get_media_thumbnail(
&book,
options.thumbnail_config.map(|config| config.format),
config,
),
(Some(book), None) => get_media_thumbnail(&book, None, config),
_ => Err(APIError::NotFound(String::from("Media not found"))),
}
get_media_thumbnail(&book.id, &book.path, image_format, config)
}

pub(crate) fn get_media_thumbnail(
media: &media::Data,
id: &str,
path: &str,
target_format: Option<ImageFormat>,
config: &StumpConfig,
) -> APIResult<(ContentType, Vec<u8>)> {
if let Some(format) = target_format {
let extension = format.extension();
let thumbnail_path = config
.get_thumbnails_dir()
.join(format!("{}.{}", media.id, extension));
.join(format!("{}.{}", id, extension));

if thumbnail_path.exists() {
tracing::trace!(path = ?thumbnail_path, media_id = ?media.id, "Found generated media thumbnail");
tracing::trace!(path = ?thumbnail_path, media_id = id, "Found generated media thumbnail");
return Ok((ContentType::from(format), read_entire_file(thumbnail_path)?));
}
} else if let Some(path) =
get_unknown_thumnail(&media.id, config.get_thumbnails_dir())
{
}

if let Some(path) = get_unknown_thumnail(id, config.get_thumbnails_dir()) {
// If there exists a file that starts with the media id in the thumbnails dir,
// then return it. This might happen if a user manually regenerates thumbnails
// via the API without updating the thumbnail config...
tracing::debug!(path = ?path, media_id = ?media.id, "Found media thumbnail that does not align with config");
tracing::debug!(path = ?path, media_id = id, "Found media thumbnail that does not align with config");
let FileParts { extension, .. } = path.file_parts();
return Ok((
ContentType::from_extension(extension.as_str()),
read_entire_file(path)?,
));
}

Ok(get_page(media.path.as_str(), 1, config)?)
Ok(get_page(path, 1, config)?)
}

// TODO: ImageResponse as body type
Expand Down
75 changes: 34 additions & 41 deletions apps/server/src/routers/api/v1/series.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ use stump_core::{
config::StumpConfig,
db::{
entity::{
macros::finished_reading_session_series_complete, LibraryOptions, Media,
Series, User, UserPermission,
macros::{
finished_reading_session_series_complete, series_or_library_thumbnail,
},
LibraryOptions, Media, Series, User, UserPermission,
},
query::{
ordering::QueryOrder,
Expand Down Expand Up @@ -505,32 +507,39 @@ async fn get_recently_added_series_handler(
}

pub(crate) fn get_series_thumbnail(
series: &series::Data,
first_book: &media::Data,
id: &str,
first_book: Option<series_or_library_thumbnail::media::Data>,
image_format: Option<ImageFormat>,
config: &StumpConfig,
) -> APIResult<(ContentType, Vec<u8>)> {
let thumbnails_dir = config.get_thumbnails_dir();
let series_id = series.id.clone();

if let Some(format) = image_format.clone() {
let extension = format.extension();
let path = thumbnails_dir.join(format!("{}.{}", series_id, extension));
let path = thumbnails_dir.join(format!("{}.{}", id, extension));

if path.exists() {
tracing::trace!(?path, series_id, "Found generated series thumbnail");
tracing::trace!(?path, id, "Found generated series thumbnail");
return Ok((ContentType::from(format), read_entire_file(path)?));
}
} else if let Some(path) = get_unknown_thumnail(&series_id, thumbnails_dir) {
tracing::debug!(path = ?path, series_id, "Found series thumbnail that does not align with config");
}

if let Some(path) = get_unknown_thumnail(id, thumbnails_dir) {
tracing::debug!(path = ?path, id, "Found series thumbnail that does not align with config");
let FileParts { extension, .. } = path.file_parts();
return Ok((
ContentType::from_extension(extension.as_str()),
read_entire_file(path)?,
));
}

get_media_thumbnail(first_book, image_format, config)
if let Some(first_book) = first_book {
get_media_thumbnail(&first_book.id, &first_book.path, image_format, config)
} else {
Err(APIError::NotFound(
"Series does not have a thumbnail".to_string(),
))
}
}

// TODO: ImageResponse type for body
Expand Down Expand Up @@ -560,52 +569,36 @@ async fn get_series_thumbnail_handler(
let age_restriction = user.age_restriction.as_ref();
let series_age_restriction = age_restriction
.map(|ar| apply_series_age_restriction(ar.age, ar.restrict_on_unset));
let where_params = chain_optional_iter(
let series_filters = chain_optional_iter(
[series::id::equals(id.clone())]
.into_iter()
.chain(apply_series_library_not_hidden_for_user_filter(&user))
.collect::<Vec<WhereParam>>(),
[series_age_restriction],
);
let book_filters = chain_optional_iter(
[],
[age_restriction
.map(|ar| apply_media_age_restriction(ar.age, ar.restrict_on_unset))],
);

let series = db
.series()
// Find the first series in the library which satisfies the age restriction
.find_first(where_params)
.with(
// Then load the first media in that series which satisfies the age restriction
series::media::fetch(chain_optional_iter(
[],
[age_restriction
.map(|ar| apply_media_age_restriction(ar.age, ar.restrict_on_unset))],
))
.take(1)
.order_by(media::name::order(Direction::Asc)),
)
.with(series::library::fetch().with(library::library_options::fetch()))
.find_first(series_filters)
.order_by(series::name::order(Direction::Asc))
.select(series_or_library_thumbnail::select(book_filters))
.exec()
.await?
.ok_or(APIError::NotFound("Series not found".to_string()))?;
let first_book = series.media.into_iter().next();

let library = series
.library()?
.ok_or(APIError::NotFound(String::from("Library relation missing")))?;

let first_book = series
.media()?
.first()
.ok_or(APIError::NotFound(String::from(
"Series does not have any media",
)))?;

let image_format = library
.library_options()
.map(LibraryOptions::from)?
.thumbnail_config
.map(|config| config.format);
let library_options = series
.library
.map(|l| l.library_options)
.map(LibraryOptions::from);
let image_format = library_options.and_then(|o| o.thumbnail_config.map(|c| c.format));

get_series_thumbnail(&series, first_book, image_format, &ctx.config)
get_series_thumbnail(&id, first_book, image_format, &ctx.config)
.map(ImageResponse::from)
}

Expand Down
Loading

0 comments on commit d574b99

Please sign in to comment.