fix: filetype, filesize in library, magic shelf, opds#2806
fix: filetype, filesize in library, magic shelf, opds#2806WorldTeacher wants to merge 8 commits intobooklore-app:developfrom
Conversation
…ServiceIntegrationTest
…um values in BookRuleEvaluatorService
|
@acx10 I'll take a look at the review tmrw |
There was a problem hiding this comment.
Pull request overview
This PR fixes broken filesize and filetype filtering functionality in the library view, magic shelves, and OPDS feeds. The root cause was that the code was attempting to access file properties directly on the Book object rather than through the primaryFile nested object. The fix updates all locations to use book.primaryFile?.fileSizeKb and book.primaryFile?.bookType, adds missing file size ranges, implements file size metadata in OPDS feeds, and consolidates comic book format options (CBR/CBZ/CB7) into the unified CBX type.
Changes:
- Updated all frontend file size and file type references to use
book.primaryFilestructure across filtering, sorting, and display components - Enhanced backend BookRuleEvaluatorService to query the bookFiles table with proper joins for FILE_SIZE and FILE_TYPE fields, including file type mapping (cbr/cbz/cb7→cbx, azw→azw3)
- Added file size attribute to OPDS acquisition links and expanded file size ranges to include 100-250 MB and 2-5 GB brackets
- Consolidated magic shelf file type dropdown from separate CBR/CBZ/CB7 options to single CBX option, and updated comprehensive test coverage for both frontend and backend
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| booklore-ui/.../libraries-summary.service.ts | Updated total size calculation to use book.primaryFile?.fileSizeKb |
| booklore-ui/.../book-rule-evaluator.service.ts | Changed file type and size extraction to use primaryFile properties |
| booklore-ui/.../book-rule-evaluator.service.spec.ts | Updated test helper to create books with primaryFile structure |
| booklore-ui/.../magic-shelf-component.ts | Consolidated CBR/CBZ/CB7 into single CBX option in file type dropdown |
| booklore-ui/.../sort.service.ts | Updated file size sorting to use book.primaryFile?.fileSizeKb |
| booklore-ui/.../sidebar-filter.ts | Updated file size filtering to use book.primaryFile?.fileSizeKb |
| booklore-ui/.../book-table.component.ts | Updated file size display to use book.primaryFile?.fileSizeKb |
| booklore-ui/.../book-filter.config.ts | Added new file size ranges (100-250 MB, 2-5 GB) and updated extractors |
| booklore-api/.../OpdsFeedService.java | Added file size in bytes to OPDS acquisition links |
| booklore-api/.../BookRuleEvaluatorService.java | Implemented joins to bookFiles table for FILE_SIZE and FILE_TYPE with mapping logic |
| booklore-api/.../BookRuleEvaluatorServiceIntegrationTest.java | Added comprehensive tests for file size and file type filtering with edge cases |
Comments suppressed due to low confidence (2)
booklore-api/src/main/java/org/booklore/service/BookRuleEvaluatorService.java:794
- Each call to getFieldExpression for FILE_SIZE or FILE_TYPE creates a new join to the bookFiles table. If a single magic shelf rule has multiple conditions on fileSize or fileType (e.g., "fileSize > 1000 AND fileSize < 5000"), this will create multiple joins which could lead to inefficient queries or unexpected behavior.
Consider caching the bookFileJoin at the query level (e.g., in the toSpecification method or buildPredicate method) and reusing it, similar to how progressJoin is created once and passed down to all predicate builders.
case FILE_SIZE -> {
Join<BookEntity, BookFileEntity> bookFileJoin = root.join("bookFiles", JoinType.LEFT);
bookFileJoin.on(cb.isTrue(bookFileJoin.get("isBookFormat")));
yield bookFileJoin.get("fileSizeKb");
}
case METADATA_SCORE -> root.get("metadataMatchScore");
case TITLE -> root.get("metadata").get("title");
case SUBTITLE -> root.get("metadata").get("subtitle");
case PUBLISHER -> root.get("metadata").get("publisher");
case PUBLISHED_DATE -> root.get("metadata").get("publishedDate");
case PAGE_COUNT -> root.get("metadata").get("pageCount");
case LANGUAGE -> root.get("metadata").get("language");
case SERIES_NAME -> root.get("metadata").get("seriesName");
case SERIES_NUMBER -> root.get("metadata").get("seriesNumber");
case SERIES_TOTAL -> root.get("metadata").get("seriesTotal");
case ISBN13 -> root.get("metadata").get("isbn13");
case ISBN10 -> root.get("metadata").get("isbn10");
case AMAZON_RATING -> root.get("metadata").get("amazonRating");
case AMAZON_REVIEW_COUNT -> root.get("metadata").get("amazonReviewCount");
case GOODREADS_RATING -> root.get("metadata").get("goodreadsRating");
case GOODREADS_REVIEW_COUNT -> root.get("metadata").get("goodreadsReviewCount");
case HARDCOVER_RATING -> root.get("metadata").get("hardcoverRating");
case HARDCOVER_REVIEW_COUNT -> root.get("metadata").get("hardcoverReviewCount");
case RANOBEDB_RATING -> root.get("metadata").get("ranobedbRating");
case AGE_RATING -> root.get("metadata").get("ageRating");
case CONTENT_RATING -> root.get("metadata").get("contentRating");
case ADDED_ON -> root.get("addedOn");
case LUBIMYCZYTAC_RATING -> root.get("metadata").get("lubimyczytacRating");
case DESCRIPTION -> root.get("metadata").get("description");
case NARRATOR -> root.get("metadata").get("narrator");
case AUDIBLE_RATING -> root.get("metadata").get("audibleRating");
case AUDIBLE_REVIEW_COUNT -> root.get("metadata").get("audibleReviewCount");
case ABRIDGED -> root.get("metadata").get("abridged");
case AUDIOBOOK_DURATION -> root.join("bookFiles", JoinType.LEFT).get("durationSeconds");
case IS_PHYSICAL -> root.get("isPhysical");
case READING_PROGRESS -> {
Expression<Float> koreader = cb.coalesce(progressJoin.get("koreaderProgressPercent"), 0f);
Expression<Float> kobo = cb.coalesce(progressJoin.get("koboProgressPercent"), 0f);
Expression<Float> pdf = cb.coalesce(progressJoin.get("pdfProgressPercent"), 0f);
Expression<Float> epub = cb.coalesce(progressJoin.get("epubProgressPercent"), 0f);
Expression<Float> cbx = cb.coalesce(progressJoin.get("cbxProgressPercent"), 0f);
yield cb.function("GREATEST", Float.class, koreader, kobo, pdf, epub, cbx);
}
case FILE_TYPE -> {
Join<BookEntity, BookFileEntity> bookFileJoin = root.join("bookFiles", JoinType.LEFT);
bookFileJoin.on(cb.isTrue(bookFileJoin.get("isBookFormat")));
yield bookFileJoin.get("bookType");
}
booklore-ui/src/app/features/magic-shelf/component/magic-shelf-component.ts:281
- The fileType dropdown options are missing the AUDIOBOOK type. According to the BookFileType enum in the backend, AUDIOBOOK is a valid book type that includes extensions like m4b, m4a, mp3, aac, flac, opus, and ogg. Users will not be able to create magic shelf rules that filter for audiobooks without this option.
Consider adding: {label: 'AUDIOBOOK', value: 'audiobook'} to the fileType array.
fileType: { label: string; value: string }[] = [
{label: 'PDF', value: 'pdf'},
{label: 'EPUB', value: 'epub'},
{label: 'CBX', value: 'cbx'},
{label: 'FB2', value: 'fb2'},
{label: 'MOBI', value: 'mobi'},
{label: 'AZW3', value: 'azw3'}
];
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| {id: 4, label: '100–250 MB', min: 102400, max: 256000, sortIndex: 4}, | ||
| {id: 5, label: '250–500 MB', min: 256000, max: 512000, sortIndex: 5}, | ||
| {id: 6, label: '0.5–1 GB', min: 512000, max: 1048576, sortIndex: 6}, | ||
| {id: 7, label: '1–2 GB', min: 1048576, max: 2097152, sortIndex: 7}, | ||
| {id: 8, label: '2–5 GB', min: 2097152, max: 5242880, sortIndex: 8}, | ||
| {id: 9, label: '5+ GB', min: 5242880, max: Infinity, sortIndex: 9} |
There was a problem hiding this comment.
Changing the IDs of existing file size ranges will break saved filters in URLs, bookmarks, and magic shelves. Users who have saved filters or magic shelves with the old range IDs (4-7) will have their filters map to incorrect ranges after this change. For example, a user who filtered for "250-500 MB" (old ID 4) will now be filtering for "100-250 MB" (new ID 4).
To avoid this breaking change, consider:
- Keep existing IDs unchanged and insert new ranges with higher IDs, OR
- Add a migration mechanism to update saved magic shelves and inform users that bookmarked URLs with filters will need to be recreated
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
booklore-api/src/main/java/org/booklore/service/BookRuleEvaluatorService.java
Show resolved
Hide resolved
…torService.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@acx10 should be ready |
📝 Description
This fixes the filetype and filesize filters in
Linked Issue: Fixes #2651
🏷️ Type of Change
🔧 Changes
book.primaryFile?.fileSizeKbinstead ofbook.fileSizeKbbook.primaryFile?.bookTypeinstead ofbook['bookType']feed.append(" length=\"").append(bookFile.getFileSizeKb() * 1024).append("\"");🧪 Testing (MANDATORY)
Manual testing steps you performed:
Regression testing:
Edge cases covered:
Test output:
Backend test output (
./gradlew test)Frontend test output (
ng test)📸 Screen Recording / Screenshots (MANDATORY)
previous state can be seen in the linked issue
after applying the PR:



library:
magic shelf (both had 0 before:
OPDS:
✅ Pre-Submission Checklist
develop(merge conflicts resolved)🤖 AI-Assisted Contributions
TODOs, or unused scaffolding left behind by AI💬 Additional Context (optional)