Files
shelfmark/backend.py
T

183 lines
5.1 KiB
Python

"""Backend logic for the book download application."""
import threading, time
import subprocess
from pathlib import Path
from typing import Dict, List, Optional, Any
from logger import setup_logger
from config import TMP_DIR, MAIN_LOOP_SLEEP_TIME, INGEST_DIR
from models import book_queue, BookInfo, QueueStatus
import book_manager
logger = setup_logger(__name__)
def search_books(query: str) -> List[Dict[str, Any]]:
"""Search for books matching the query.
Args:
query: Search term
Returns:
List[Dict]: List of book information dictionaries
"""
try:
books = book_manager.search_books(query)
return [_book_info_to_dict(book) for book in books]
except Exception as e:
logger.error(f"Error searching books: {e}")
return []
def get_book_info(book_id: str) -> Optional[Dict[str, Any]]:
"""Get detailed information for a specific book.
Args:
book_id: Book identifier
Returns:
Optional[Dict]: Book information dictionary if found
"""
try:
book = book_manager.get_book_info(book_id)
return _book_info_to_dict(book)
except Exception as e:
logger.error(f"Error getting book info: {e}")
return None
def queue_book(book_id: str) -> bool:
"""Add a book to the download queue.
Args:
book_id: Book identifier
Returns:
bool: True if book was successfully queued
"""
try:
book_info = book_manager.get_book_info(book_id)
book_queue.add(book_id, book_info)
logger.info(f"Book queued: {book_info.title}")
return True
except Exception as e:
logger.error(f"Error queueing book: {e}")
return False
def queue_status() -> Dict[str, Dict[str, Any]]:
"""Get current status of the download queue.
Returns:
Dict: Queue status organized by status type
"""
status = book_queue.get_status()
# Convert Enum keys to strings and properly format the response
return {
status_type.value: books
for status_type, books in status.items()
}
def get_book_data(book_id: str) -> Optional[bytes]:
"""Get book data for a specific book.
Args:
book_id: Book identifier
Returns:
Optional[bytes]: Book data if available
"""
try:
book_info = book_queue._book_data[book_id]
path = INGEST_DIR / f"{book_id}.epub"
with open(path, "rb") as f:
return f.read(), book_info.title
except Exception as e:
logger.error(f"Error getting book data: {e}")
return None
def _book_info_to_dict(book: BookInfo) -> Dict[str, Any]:
"""Convert BookInfo object to dictionary representation."""
return {
key: value for key, value in book.__dict__.items()
if value is not None
}
def _process_book(book_path: str) -> bool:
"""Check if downloaded book is valid.
Args:
book_path: Path to downloaded book file
Returns:
bool: True if book is valid
"""
try:
script_path = Path(__file__).parent / "check_health.sh"
result = subprocess.run(
[str(script_path), book_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
return result.returncode == 0
except Exception as e:
logger.error(f"Error checking book health: {e}")
return False
def _download_book(book_id: str) -> bool:
"""Download and process a book.
Args:
book_id: Book identifier
Returns:
bool: True if download and processing successful
"""
try:
book_info = book_queue._book_data[book_id]
data = book_manager.download_book(book_info)
if not data:
raise Exception("No data received")
book_path = TMP_DIR / f"{book_id}.{book_info.format}"
with open(book_path, "wb") as f:
f.write(data.getbuffer())
return _process_book(str(book_path))
except Exception as e:
logger.error(f"Error downloading book: {e}")
return False
def download_loop():
"""Background thread for processing download queue."""
logger.info("Starting download loop")
while True:
book_id = book_queue.get_next()
if not book_id:
time.sleep(MAIN_LOOP_SLEEP_TIME)
continue
try:
book_queue.update_status(book_id, QueueStatus.DOWNLOADING)
success = _download_book(book_id)
new_status = (
QueueStatus.AVAILABLE if success else QueueStatus.ERROR
)
book_queue.update_status(book_id, new_status)
logger.info(
f"Book {book_id} download {'successful' if success else 'failed'}"
)
except Exception as e:
logger.error(f"Error in download loop: {e}")
book_queue.update_status(book_id, QueueStatus.ERROR)
# Start download loop in background thread
download_thread = threading.Thread(
target=download_loop,
daemon=True
)
download_thread.start()