sync errors

This commit is contained in:
2026-05-10 21:06:33 +02:00
parent dfc9fa3800
commit 877c4c9d67
2 changed files with 31 additions and 9 deletions
+7 -1
View File
@@ -7,7 +7,7 @@ import config
import db import db
import extractor import extractor
import sftp as sftp_module import sftp as sftp_module
from uploader import CalibreClient from uploader import CalibreClient, CalibreUnavailableError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -86,6 +86,7 @@ def run_sync(limit: int | None = None) -> None:
t2 = time.monotonic() t2 = time.monotonic()
status = client.upload(book, zip_source=remote_zip.remote_path) status = client.upload(book, zip_source=remote_zip.remote_path)
log.info("Upload '%s'%s (%.1fs)", book.name, status, time.monotonic() - t2) log.info("Upload '%s'%s (%.1fs)", book.name, status, time.monotonic() - t2)
time.sleep(2)
if status == "uploaded": if status == "uploaded":
counters["books_uploaded"] += 1 counters["books_uploaded"] += 1
elif status == "skipped_duplicate": elif status == "skipped_duplicate":
@@ -99,6 +100,11 @@ def run_sync(limit: int | None = None) -> None:
zip_error = f"{books_errored_this_zip} book upload(s) failed — will retry next sync" zip_error = f"{books_errored_this_zip} book upload(s) failed — will retry next sync"
extractor.cleanup(work_dir / "extracted" / local_zip.stem) extractor.cleanup(work_dir / "extracted" / local_zip.stem)
except CalibreUnavailableError as e:
log.error("Calibre-Web unavailable — aborting sync run: %s", e)
db.mark_zip_processed(remote_zip.remote_path, remote_zip.file_size, "error", str(e))
db.finish_sync_run(run_id, status="error", error_msg=str(e), **counters)
return
except Exception as e: except Exception as e:
log.error("Error processing %s: %s", remote_zip.remote_path, e) log.error("Error processing %s: %s", remote_zip.remote_path, e)
zip_status = "error" zip_status = "error"
+23 -7
View File
@@ -25,12 +25,17 @@ _JUNK_WORDS = {
} }
class CalibreUnavailableError(RuntimeError):
"""Raised when Calibre-Web returns repeated 502/503/504 — sync run should abort."""
class CalibreClient: class CalibreClient:
def __init__(self, cfg: CalibreConfig): def __init__(self, cfg: CalibreConfig):
self._cfg = cfg self._cfg = cfg
self._session = requests.Session() self._session = requests.Session()
self._authenticated = False self._authenticated = False
self._upload_csrf: str | None = None self._upload_csrf: str | None = None
self._consecutive_failures = 0
def _ensure_auth(self) -> None: def _ensure_auth(self) -> None:
if self._authenticated: if self._authenticated:
@@ -62,6 +67,7 @@ class CalibreClient:
try: try:
resp = self._session.get( resp = self._session.get(
f"{self._cfg.url}/opds/search/{quote(query, safe='')}", f"{self._cfg.url}/opds/search/{quote(query, safe='')}",
auth=(self._cfg.user, self._cfg.password),
timeout=15, timeout=15,
) )
if resp.status_code == 404: if resp.status_code == 404:
@@ -102,7 +108,6 @@ class CalibreClient:
return "skipped_duplicate" return "skipped_duplicate"
mime = MIME_TYPES.get(book_path.suffix.lower(), "application/octet-stream") mime = MIME_TYPES.get(book_path.suffix.lower(), "application/octet-stream")
last_err: Exception | None = None
for attempt in range(1, 4): for attempt in range(1, 4):
try: try:
with book_path.open("rb") as fh: with book_path.open("rb") as fh:
@@ -116,15 +121,23 @@ class CalibreClient:
log.error("Upload HTTP %s (attempt %d/3) — body: %s", resp.status_code, attempt, resp.text[:300]) log.error("Upload HTTP %s (attempt %d/3) — body: %s", resp.status_code, attempt, resp.text[:300])
resp.raise_for_status() resp.raise_for_status()
log.info("Uploaded: %s", book_path.name) log.info("Uploaded: %s", book_path.name)
self._consecutive_failures = 0
db.record_book(book_path.name, file_hash, zip_source, "uploaded") db.record_book(book_path.name, file_hash, zip_source, "uploaded")
return "uploaded" return "uploaded"
except requests.HTTPError as e: except requests.HTTPError:
last_err = e if resp.status_code in (502, 503, 504):
if resp.status_code in (502, 503, 504) and attempt < 3: if attempt < 3:
delay = 60 log.warning("HTTP %s on attempt %d/3 — retrying in 60s ...", resp.status_code, attempt)
log.warning("HTTP %s on attempt %d/3 — retrying in %ds ...", resp.status_code, attempt, delay) time.sleep(60)
time.sleep(delay)
continue continue
# All retries exhausted
self._consecutive_failures += 1
if self._consecutive_failures >= 3:
raise CalibreUnavailableError(
f"Calibre-Web returned {resp.status_code} on {self._consecutive_failures} "
"consecutive books — aborting sync run"
)
break
if resp.status_code == 400 and attempt == 1: if resp.status_code == 400 and attempt == 1:
log.warning("HTTP 400 — CSRF token likely expired, re-authenticating ...") log.warning("HTTP 400 — CSRF token likely expired, re-authenticating ...")
self._authenticated = False self._authenticated = False
@@ -135,6 +148,9 @@ class CalibreClient:
db.record_book(book_path.name, file_hash, zip_source, "error") db.record_book(book_path.name, file_hash, zip_source, "error")
return "error" return "error"
except CalibreUnavailableError:
db.record_book(book_path.name, file_hash, zip_source, "error")
raise
except Exception as e: except Exception as e:
log.error("Upload failed for %s: %s", book_path.name, e) log.error("Upload failed for %s: %s", book_path.name, e)
db.record_book(book_path.name, file_hash, zip_source, "error") db.record_book(book_path.name, file_hash, zip_source, "error")