sync errors
This commit is contained in:
@@ -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
@@ -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")
|
||||||
|
|||||||
Reference in New Issue
Block a user