Compare commits

...
1 Commits
Author SHA1 Message Date
CaliBrain c8f21b8f8d Fix progression in download (#248) 2025-08-25 23:59:05 -04:00
4 changed files with 27 additions and 30 deletions
+6 -18
View File
@@ -144,16 +144,9 @@ def _download_book_with_cancellation(book_id: str, cancel_flag: Event) -> Option
if cancel_flag.is_set():
logger.info(f"Download cancelled before book manager call: {book_id}")
return None
# Update progress periodically during download
progress_thread = threading.Thread(
target=_update_download_progress,
args=(book_id, cancel_flag),
daemon=True
)
progress_thread.start()
success = book_manager.download_book(book_info, book_path)
progress_callback = lambda progress: update_download_progress(book_id, progress)
success = book_manager.download_book(book_info, book_path, progress_callback, cancel_flag)
# Stop progress updates
cancel_flag.wait(0.1) # Brief pause for progress thread cleanup
@@ -209,14 +202,9 @@ def _download_book_with_cancellation(book_id: str, cancel_flag: Event) -> Option
logger.error_trace(f"Error downloading book: {e}")
return None
def _update_download_progress(book_id: str, cancel_flag: Event) -> None:
"""Update download progress periodically."""
progress = 0.0
while not cancel_flag.is_set() and progress < 100.0:
# Simulate progress (in real implementation, this would get actual progress)
progress = min(100.0, progress + 10.0)
book_queue.update_progress(book_id, progress)
time.sleep(DOWNLOAD_PROGRESS_UPDATE_INTERVAL)
def update_download_progress(book_id: str, progress: float) -> None:
"""Update download progress."""
book_queue.update_progress(book_id, progress)
def cancel_download(book_id: str) -> bool:
"""Cancel a download.
+13 -9
View File
@@ -3,7 +3,8 @@
import time, json, re
from pathlib import Path
from urllib.parse import quote
from typing import List, Optional, Dict, Union
from typing import List, Optional, Dict, Union, Callable
from threading import Event
from bs4 import BeautifulSoup, Tag, NavigableString, ResultSet
import downloader
@@ -11,10 +12,10 @@ from logger import setup_logger
from config import SUPPORTED_FORMATS, BOOK_LANGUAGE, AA_BASE_URL
from env import AA_DONATOR_KEY, USE_CF_BYPASS, PRIORITIZE_WELIB
from models import BookInfo, SearchFilters
logger = setup_logger(__name__)
def search_books(query: str, filters: SearchFilters) -> List[BookInfo]:
"""Search for books matching the query.
@@ -198,7 +199,7 @@ def _parse_book_info_page(soup: BeautifulSoup, book_id: str) -> BookInfo:
):
libgen_url = url["href"]
# TODO : Temporary fix ? Maybe get URLs from https://open-slum.org/ ?
libgen_url = libgen_url = re.sub(r'libgen\.(\w+)', 'libgen.gs', url["href"])
libgen_url = libgen_url = re.sub(r'libgen\.(\w+)', 'libgen.la', url["href"])
external_urls_libgen.add(libgen_url)
elif url.text.strip().lower().startswith("z-lib"):
if ".onion/" not in url["href"]:
@@ -300,7 +301,7 @@ def _extract_book_metadata(
}
def download_book(book_info: BookInfo, book_path: Path) -> bool:
def download_book(book_info: BookInfo, book_path: Path, progress_callback: Optional[Callable[[float], None]] = None, cancel_flag: Optional[Event] = None) -> bool:
"""Download a book from available sources.
Args:
@@ -324,10 +325,11 @@ def download_book(book_info: BookInfo, book_path: Path) -> bool:
for link in download_links:
try:
download_url = _get_download_url(link, book_info.title)
download_url = _get_download_url(link, book_info.title, cancel_flag)
if download_url != "":
logger.info(f"Downloading `{book_info.title}` from `{download_url}`")
data = downloader.download_url(download_url, book_info.size or "")
data = downloader.download_url(download_url, book_info.size or "", progress_callback, cancel_flag)
if not data:
raise Exception("No data received")
@@ -344,7 +346,7 @@ def download_book(book_info: BookInfo, book_path: Path) -> bool:
return False
def _get_download_url(link: str, title: str) -> str:
def _get_download_url(link: str, title: str, cancel_flag: Optional[Event] = None) -> str:
"""Extract actual download URL from various source pages."""
url = ""
@@ -371,8 +373,10 @@ def _get_download_url(link: str, title: str) -> str:
if countdown:
sleep_time = int(countdown[0].text)
logger.info(f"Waiting {sleep_time}s for {title}")
time.sleep(sleep_time)
url = _get_download_url(link, title)
if cancel_flag is not None and cancel_flag.wait(timeout=sleep_time):
logger.info(f"Cancelled wait for {title}")
return ""
url = _get_download_url(link, title, cancel_flag)
else:
url = download_links[0]["href"]
else:
-1
View File
@@ -80,7 +80,6 @@ def _is_bypassed(sb) -> bool:
"please wait",
"ddos protection",
"security check",
"browser check",
"moment please",
"hold on",
"loading",
+8 -2
View File
@@ -8,7 +8,8 @@ from io import BytesIO
from typing import Optional
from urllib.parse import urlparse
from tqdm import tqdm
from typing import Callable
from threading import Event
from logger import setup_logger
from config import PROXIES
from env import MAX_RETRY, DEFAULT_SLEEP, USE_CF_BYPASS
@@ -71,7 +72,7 @@ def html_get_page(url: str, retry: int = MAX_RETRY, use_bypasser: bool = False)
time.sleep(sleep_time)
return html_get_page(url, retry - 1, use_bypasser)
def download_url(link: str, size: str = "") -> Optional[BytesIO]:
def download_url(link: str, size: str = "", progress_callback: Optional[Callable[[float], None]] = None, cancel_flag: Optional[Event] = None) -> Optional[BytesIO]:
"""Download content from URL into a BytesIO buffer.
Args:
@@ -99,6 +100,11 @@ def download_url(link: str, size: str = "") -> Optional[BytesIO]:
for chunk in response.iter_content(chunk_size=1000):
buffer.write(chunk)
pbar.update(len(chunk))
if progress_callback is not None:
progress_callback(pbar.n * 100.0 / total_size)
if cancel_flag is not None and cancel_flag.is_set():
logger.info(f"Download cancelled: {link}")
return None
pbar.close()
if buffer.tell() * 0.1 < total_size * 0.9: