mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-09-24 20:30:31 +01:00
183 lines
5.1 KiB
Python
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()
|