diff --git a/abogen/pronunciation_store.py b/abogen/pronunciation_store.py index 88524ff..b8f0300 100644 --- a/abogen/pronunciation_store.py +++ b/abogen/pronunciation_store.py @@ -1,6 +1,7 @@ from __future__ import annotations import sqlite3 +import shutil import threading import time from pathlib import Path @@ -20,6 +21,16 @@ def _store_path() -> Path: base_dir = Path(get_internal_cache_path("pronunciations")) target = base_dir / "pronunciations.db" target.parent.mkdir(parents=True, exist_ok=True) + + if not target.exists(): + try: + legacy_dir = Path(get_internal_cache_path("pronunciations")) + legacy_path = legacy_dir / "pronunciations.db" + if legacy_path.exists() and legacy_path != target: + shutil.move(str(legacy_path), target) + except Exception: + pass + return target diff --git a/abogen/web/routes/api.py b/abogen/web/routes/api.py index 73bb9ba..26a5781 100644 --- a/abogen/web/routes/api.py +++ b/abogen/web/routes/api.py @@ -1,7 +1,7 @@ from typing import Any, Dict, Mapping, List, Optional import base64 -from flask import Blueprint, request, jsonify, send_file +from flask import Blueprint, request, jsonify, send_file, url_for from flask.typing import ResponseReturnValue from abogen.web.routes.utils.settings import ( @@ -24,6 +24,8 @@ from abogen.llm_client import list_models, LLMClientError from abogen.kokoro_text_normalization import normalize_for_pipeline from abogen.integrations.audiobookshelf import AudiobookshelfClient, AudiobookshelfConfig from abogen.integrations.calibre_opds import CalibreOPDSClient +from abogen.web.routes.utils.service import get_service +from werkzeug.utils import secure_filename api_bp = Blueprint("api", __name__) @@ -140,6 +142,78 @@ def api_calibre_opds_test() -> ResponseReturnValue: except Exception as e: return jsonify({"error": str(e)}), 400 +@api_bp.post("/integrations/calibre-opds/import") +def api_calibre_opds_import() -> ResponseReturnValue: + if not request.is_json: + return jsonify({"error": "Expected JSON payload."}), 400 + + payload = request.get_json(force=True, silent=True) or {} + href = payload.get("href") + + if not href: + return jsonify({"error": "Download URL (href) is required."}), 400 + + settings = load_settings() + integrations = settings.get("integrations", {}) + calibre_settings = integrations.get("calibre_opds", {}) + + # We need to download the file + # For now, let's just return a success message as a placeholder + # In a real implementation, this would trigger a download job + + # Re-using the logic from books.py if possible, or implementing it here + # It seems books.py had a placeholder for this too. + + # Let's try to actually download it using the service + try: + service = get_service() + + client = CalibreOPDSClient( + base_url=calibre_settings.get("base_url", ""), + username=calibre_settings.get("username"), + password=calibre_settings.get("password"), + verify=calibre_settings.get("verify_ssl", True), + ) + + with client._open_client() as http_client: + response = http_client.get(href, follow_redirects=True) + response.raise_for_status() + + # Try to get filename from content-disposition + filename = "downloaded_book.epub" + if "content-disposition" in response.headers: + import re + fname = re.findall("filename=(.+)", response.headers["content-disposition"]) + if fname: + filename = fname[0].strip('"') + + filename = secure_filename(filename) + + # Save to uploads folder + uploads_dir = service._uploads_root + target_path = uploads_dir / filename + + # Ensure unique filename + stem = target_path.stem + suffix = target_path.suffix + counter = 1 + while target_path.exists(): + target_path = uploads_dir / f"{stem}_{counter}{suffix}" + counter += 1 + + with open(target_path, "wb") as f: + for chunk in response.iter_bytes(chunk_size=8192): + f.write(chunk) + + return jsonify({ + "success": True, + "message": "Book downloaded successfully.", + "redirect": url_for("main.prepare_page", filename=target_path.name) + }) + + except Exception as e: + return jsonify({"error": str(e)}), 500 + # --- LLM Routes --- @api_bp.post("/llm/models")