cwa import
This commit is contained in:
@@ -12,8 +12,6 @@ import config
|
||||
import db
|
||||
import sftp as sftp_module
|
||||
import sync
|
||||
import uploader
|
||||
from uploader import CalibreClient, delete_book, fetch_all_books, find_duplicate_groups
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s — %(message)s")
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -77,23 +75,6 @@ async def dashboard(request: Request):
|
||||
})
|
||||
|
||||
|
||||
# --- Books ---
|
||||
|
||||
@app.get("/books", response_class=HTMLResponse)
|
||||
async def books_page(request: Request, page: int = 1):
|
||||
per_page = 50
|
||||
offset = (page - 1) * per_page
|
||||
books = [dict(b) for b in db.get_books(limit=per_page, offset=offset)]
|
||||
total = db.get_books_count()
|
||||
pages = max(1, (total + per_page - 1) // per_page)
|
||||
return templates.TemplateResponse(request, "books.html", {
|
||||
"books": books,
|
||||
"page": page,
|
||||
"pages": pages,
|
||||
"total": total,
|
||||
})
|
||||
|
||||
|
||||
# --- Settings ---
|
||||
|
||||
@app.get("/settings", response_class=HTMLResponse)
|
||||
@@ -117,10 +98,8 @@ async def save_settings(
|
||||
sftp_key: str = Form(""),
|
||||
sftp_password: str = Form(""),
|
||||
sftp_remote_path: str = Form(""),
|
||||
calibre_url: str = Form(""),
|
||||
calibre_user: str = Form(""),
|
||||
calibre_pass: str = Form(""),
|
||||
local_work_dir: str = Form("/tmp/calibresync"),
|
||||
work_dir: str = Form("/tmp/calibresync"),
|
||||
import_dir: str = Form(""),
|
||||
scheduler_interval_minutes: str = Form("0"),
|
||||
sync_batch_size: str = Form("0"),
|
||||
):
|
||||
@@ -132,10 +111,8 @@ async def save_settings(
|
||||
"sftp_key": sftp_key,
|
||||
"sftp_password": sftp_password,
|
||||
"sftp_remote_path": sftp_remote_path,
|
||||
"calibre_url": calibre_url,
|
||||
"calibre_user": calibre_user,
|
||||
"calibre_pass": calibre_pass,
|
||||
"local_work_dir": local_work_dir,
|
||||
"work_dir": work_dir,
|
||||
"import_dir": import_dir,
|
||||
"scheduler_interval_minutes": scheduler_interval_minutes,
|
||||
"sync_batch_size": sync_batch_size,
|
||||
})
|
||||
@@ -179,111 +156,6 @@ async def test_ssh():
|
||||
return {"ok": ok, "message": message}
|
||||
|
||||
|
||||
@app.get("/api/test/calibre")
|
||||
async def test_calibre():
|
||||
cfg = config.load()
|
||||
ok, message = uploader.test_connection(cfg.calibre)
|
||||
return {"ok": ok, "message": message}
|
||||
|
||||
|
||||
# --- Duplicates ---
|
||||
|
||||
@app.get("/duplicates", response_class=HTMLResponse)
|
||||
async def duplicates_page(request: Request):
|
||||
cfg = config.load()
|
||||
error = None
|
||||
groups: list = []
|
||||
total_books = 0
|
||||
try:
|
||||
books = fetch_all_books(cfg.calibre)
|
||||
total_books = len(books)
|
||||
groups = find_duplicate_groups(books)
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
return templates.TemplateResponse(request, "duplicates.html", {
|
||||
"groups": groups,
|
||||
"total_books": total_books,
|
||||
"error": error,
|
||||
})
|
||||
|
||||
|
||||
@app.post("/api/delete_book/{book_id}")
|
||||
async def delete_book_api(book_id: int):
|
||||
cfg = config.load()
|
||||
ok, message = delete_book(cfg.calibre, book_id)
|
||||
return {"ok": ok, "message": message}
|
||||
|
||||
|
||||
_dedup_state: dict = {"running": False, "deleted": 0, "failed": 0, "total": 0, "done": False, "error": None}
|
||||
|
||||
|
||||
def _run_dedup():
|
||||
global _dedup_state
|
||||
try:
|
||||
cfg = config.load()
|
||||
log.info("Dedup: fetching all books ...")
|
||||
client = CalibreClient(cfg.calibre)
|
||||
client._ensure_auth()
|
||||
books = fetch_all_books(cfg.calibre)
|
||||
groups = find_duplicate_groups(books)
|
||||
to_delete = [b for group in groups for b in sorted(group, key=lambda x: x.get("id", 0))[1:]]
|
||||
_dedup_state.update({"total": len(to_delete), "deleted": 0, "failed": 0})
|
||||
log.info("Dedup: %d duplicate(s) to delete across %d group(s)", len(to_delete), len(groups))
|
||||
for book in to_delete:
|
||||
ok, msg = delete_book(cfg.calibre, book["id"], client)
|
||||
if ok:
|
||||
_dedup_state["deleted"] += 1
|
||||
else:
|
||||
_dedup_state["failed"] += 1
|
||||
log.warning("Dedup: failed to delete book %d: %s", book["id"], msg)
|
||||
if _dedup_state["deleted"] % 10 == 0:
|
||||
log.info("Dedup progress: %d / %d deleted", _dedup_state["deleted"], _dedup_state["total"])
|
||||
log.info("Dedup done: %d deleted, %d failed", _dedup_state["deleted"], _dedup_state["failed"])
|
||||
except Exception as e:
|
||||
log.error("Dedup error: %s", e)
|
||||
_dedup_state["error"] = str(e)
|
||||
finally:
|
||||
_dedup_state["running"] = False
|
||||
_dedup_state["done"] = True
|
||||
|
||||
|
||||
@app.post("/api/delete_duplicates")
|
||||
async def delete_duplicates_api(background_tasks: BackgroundTasks):
|
||||
if _dedup_state["running"]:
|
||||
return {"ok": False, "message": "Already running"}
|
||||
_dedup_state.update({"running": True, "deleted": 0, "failed": 0, "total": 0, "done": False, "error": None})
|
||||
background_tasks.add_task(_run_dedup)
|
||||
return {"ok": True, "message": "Started"}
|
||||
|
||||
|
||||
@app.get("/api/delete_duplicates/status")
|
||||
async def delete_duplicates_status():
|
||||
return _dedup_state
|
||||
|
||||
|
||||
@app.get("/api/debug/calibre_books")
|
||||
async def debug_calibre_books():
|
||||
"""Show raw Calibre-Web listbooks response shape so we can identify field names."""
|
||||
cfg = config.load()
|
||||
from uploader import CalibreClient
|
||||
client = CalibreClient(cfg.calibre)
|
||||
client._ensure_auth()
|
||||
resp = client._session.get(
|
||||
f"{cfg.calibre.url}/ajax/listbooks",
|
||||
params={"draw": 1, "start": 0, "length": 5, "sort": "title", "order": "asc"},
|
||||
timeout=30,
|
||||
)
|
||||
data = resp.json()
|
||||
non_list = {k: v for k, v in data.items() if not isinstance(v, list)}
|
||||
list_keys = {k: len(v) for k, v in data.items() if isinstance(v, list)}
|
||||
return {
|
||||
"http_status": resp.status_code,
|
||||
"top_level_keys": list(data.keys()),
|
||||
"non_list_fields": non_list,
|
||||
"list_fields_lengths": list_keys,
|
||||
}
|
||||
|
||||
|
||||
# --- Data reset ---
|
||||
|
||||
@app.post("/settings/reset-sync-data")
|
||||
|
||||
Reference in New Issue
Block a user