Handle optional book.path to support books without files
Books may not have a path (e.g., physical books, metadata-only entries). Updated path-dependent operations to handle None gracefully: - get_file: raise ValueError if book has no path - update_book: skip path relocation if no path exists - remove_files: skip filesystem cleanup if no path exists Also fixed _save_book_files return type and removed unused imports.
This commit is contained in:
@@ -38,18 +38,14 @@ from chitai.database.models import (
|
||||
BookSeries,
|
||||
FileMetadata,
|
||||
Identifier,
|
||||
BookList,
|
||||
Library,
|
||||
)
|
||||
from chitai.database.models.book_progress import BookProgress
|
||||
from chitai.schemas.book import BooksCreateFromFiles
|
||||
from chitai.services.filesystem_library import BookPathGenerator
|
||||
from chitai.services.metadata_extractor import Extractor as MetadataExtractor
|
||||
from chitai.services.utils import (
|
||||
cleanup_empty_parent_directories,
|
||||
delete_directory,
|
||||
delete_file,
|
||||
is_empty,
|
||||
move_dir_contents,
|
||||
move_file,
|
||||
save_image,
|
||||
@@ -262,6 +258,9 @@ class BookService(SQLAlchemyAsyncRepositoryService[Book]):
|
||||
ValueError: If the file is missing or not found for the given book.
|
||||
"""
|
||||
book = await self.get(book_id)
|
||||
if book.path is None:
|
||||
raise ValueError("Cannot download file: book has no path")
|
||||
|
||||
for file in book.files:
|
||||
if file.id != file_id:
|
||||
continue
|
||||
@@ -342,14 +341,15 @@ class BookService(SQLAlchemyAsyncRepositoryService[Book]):
|
||||
await self._save_cover_image(data)
|
||||
|
||||
# TODO: extract out into its own function _update_book_path
|
||||
# Check if file path must be updated
|
||||
path_gen = BookPathGenerator(library.root_path)
|
||||
updated_path = path_gen.generate_path(book.to_dict() | data)
|
||||
if str(updated_path) != book.path:
|
||||
# TODO: Move only the files associated with the book instead of the whole directory
|
||||
await move_dir_contents(book.path, updated_path)
|
||||
data["path"] = str(updated_path)
|
||||
cleanup_empty_parent_directories(Path(book.path), Path(library.root_path))
|
||||
# Check if file path must be updated (only for books with files)
|
||||
if book.path is not None:
|
||||
path_gen = BookPathGenerator(library.root_path)
|
||||
updated_path = path_gen.generate_path(book.to_dict() | data)
|
||||
if str(updated_path) != book.path:
|
||||
# TODO: Move only the files associated with the book instead of the whole directory
|
||||
await move_dir_contents(book.path, updated_path)
|
||||
data["path"] = str(updated_path)
|
||||
cleanup_empty_parent_directories(Path(book.path), Path(library.root_path))
|
||||
|
||||
return await super().update(data, item_id=book_id, execution_options={"populate_existing": True})
|
||||
|
||||
@@ -367,8 +367,8 @@ class BookService(SQLAlchemyAsyncRepositoryService[Book]):
|
||||
book = await self.get(book_id)
|
||||
data = book.to_dict()
|
||||
data["files"] = files
|
||||
await self._save_book_files(library, data)
|
||||
book.files.extend(data["files"])
|
||||
new_files = await self._save_book_files(library, data)
|
||||
book.files.extend(new_files)
|
||||
await self.update_book(book.id, {"files": [file for file in book.files]}, library)
|
||||
|
||||
async def remove_files(
|
||||
@@ -387,7 +387,7 @@ class BookService(SQLAlchemyAsyncRepositoryService[Book]):
|
||||
"""
|
||||
book = await self.get_one(Book.id == book_id, Book.library_id == library.id)
|
||||
|
||||
if delete_files:
|
||||
if delete_files and book.path is not None:
|
||||
# TODO: Extract this out into its own function
|
||||
for file in (file for file in book.files if file.id in file_ids):
|
||||
full_path = Path(book.path) / Path(file.path)
|
||||
@@ -446,7 +446,7 @@ class BookService(SQLAlchemyAsyncRepositoryService[Book]):
|
||||
|
||||
return data
|
||||
|
||||
async def _populate_with_unique_relationships(self, data: ModelDictT[Book]):
|
||||
async def _populate_with_unique_relationships(self, data: ModelDictT[Book]) -> ModelDictT[Book]:
|
||||
"""
|
||||
Ensure relationship entities (authors, series, tags, etc.) are unique in the database.
|
||||
|
||||
@@ -508,7 +508,7 @@ class BookService(SQLAlchemyAsyncRepositoryService[Book]):
|
||||
|
||||
return model_data
|
||||
|
||||
async def _save_book_files(self, library: Library, data: dict) -> dict:
|
||||
async def _save_book_files(self, library: Library, data: dict) -> list[FileMetadata]:
|
||||
"""
|
||||
Save uploaded book files to the filesystem.
|
||||
|
||||
@@ -558,7 +558,7 @@ class BookService(SQLAlchemyAsyncRepositoryService[Book]):
|
||||
)
|
||||
|
||||
data["files"] = file_metadata
|
||||
return data
|
||||
return data["files"]
|
||||
|
||||
async def _parse_metadata_from_files(self, data: dict, root_path: Path | None = None) -> dict:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user