mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-01 22:06:39 +01:00
The Calibre dependency was due to the script testing for validity of the downloaded file, as often they would be corrupted from aa. But CWA is already doing that, so we are just having redundant code here. For the cloudflarebypasser, I basically run my own version now, instead of depending on an external library, this way we have better control for debugging and on the docker image. Fixes #18, #33, #27, #48, #65, #78, #86, #88, #89 --------- Co-authored-by: mik593 <91991279+mik593@users.noreply.github.com>
162 lines
4.7 KiB
Python
162 lines
4.7 KiB
Python
"""Backend logic for the book download application."""
|
|
|
|
import threading, time
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Any, Tuple
|
|
|
|
from logger import setup_logger
|
|
from config import TMP_DIR, MAIN_LOOP_SLEEP_TIME, INGEST_DIR
|
|
from models import book_queue, BookInfo, QueueStatus, SearchFilters
|
|
import book_manager
|
|
|
|
logger = setup_logger(__name__)
|
|
|
|
def search_books(query: str, filters: SearchFilters) -> List[Dict[str, Any]]:
|
|
"""Search for books matching the query.
|
|
|
|
Args:
|
|
query: Search term
|
|
filters: Search filters object
|
|
|
|
Returns:
|
|
List[Dict]: List of book information dictionaries
|
|
"""
|
|
try:
|
|
books = book_manager.search_books(query, filters)
|
|
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) -> Tuple[Optional[bytes], str] :
|
|
"""Get book data for a specific book, including its title.
|
|
|
|
Args:
|
|
book_id: Book identifier
|
|
|
|
Returns:
|
|
Tuple[Optional[bytes], str]: Book data if available, and the book title
|
|
"""
|
|
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 _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]
|
|
book_path = TMP_DIR / f"{book_id}.{book_info.format}"
|
|
|
|
success = book_manager.download_book(book_info, book_path)
|
|
if not success:
|
|
raise Exception("Unkown error downloading book")
|
|
|
|
final_path = INGEST_DIR / f"{book_id}.{book_info.format}"
|
|
|
|
shutil.move(book_path, final_path)
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"Error downloading book: {e}")
|
|
return False
|
|
|
|
def download_loop() -> None:
|
|
"""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()
|