diff --git a/bookscraper/app.py b/bookscraper/app.py index b560ca2..dcf4c3a 100644 --- a/bookscraper/app.py +++ b/bookscraper/app.py @@ -358,28 +358,14 @@ from db.repository import get_book_state @app.route("/inspect/statuscheck/", methods=["POST"]) +@logcall def inspect_statuscheck(book_idx): try: - statuscheck_result = StatusCheckService.run(book_idx) - - repo_state = get_book_state(book_idx) - - return render_template( - "inspect/statuscheck_result.html", - result=statuscheck_result, - repo_state=repo_state, - ) - + StatusCheckService.run(book_idx) + return ("", 204) # background action, geen UI except Exception as e: log(f"[STATUSCHECK] ERROR book_idx={book_idx}: {e}") - return ( - render_template( - "inspect/statuscheck_result.html", - error=str(e), - book_idx=book_idx, - ), - 500, - ) + return jsonify({"error": str(e)}), 500 # ===================================================== diff --git a/bookscraper/db/repository.py b/bookscraper/db/repository.py index d64f25d..b58b7bb 100644 --- a/bookscraper/db/repository.py +++ b/bookscraper/db/repository.py @@ -373,5 +373,18 @@ def get_book_state(book_idx): state["downloaded"] = merged_downloaded state["audio_done"] = merged_audio_done state["chapters_total"] = chapters_total + # ---------------------------------------------------- + # 4b. Derive status (READ-ONLY) + # ---------------------------------------------------- + derived_status = sqlite_row.get("status") or "unknown" + + if chapters_total > 0: + if merged_downloaded < chapters_total: + derived_status = "downloading" + elif merged_downloaded == chapters_total and merged_audio_done < chapters_total: + derived_status = "audio" + elif merged_audio_done == chapters_total: + derived_status = "done" + state["status"] = derived_status return state diff --git a/bookscraper/scraper/scriptgen.py b/bookscraper/scraper/scriptgen.py index 2b24395..eb5232d 100644 --- a/bookscraper/scraper/scriptgen.py +++ b/bookscraper/scraper/scriptgen.py @@ -5,7 +5,6 @@ import os import stat from logbus.publisher import log - from scraper.logger_decorators import logcall TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "templates") @@ -45,26 +44,40 @@ def detect_volumes(book_base: str): # ------------------------------------------------------------ def build_merge_block(title: str, author: str, volumes): lines = [] + + # -------------------------------------------------------- + # Normalize input (defensive) + # -------------------------------------------------------- + title = (title or "").strip() + author = (author or "").strip() + total_vols = len(volumes) + + # Padding-regel: + # - altijd minimaal 2 (01, 02) + # - 3 bij >=100 if total_vols >= 100: pad = 3 - elif total_vols >= 10: - pad = 2 else: - pad = 0 + pad = 2 for num, dirname in volumes: - if pad > 0: - vol_num = f"{num:0{pad}d}" - else: - vol_num = str(num) + vol_num = f"{num:0{pad}d}" # voor filename + series_part = f"{num:0{pad}d}" # voor series-part (string!) line = ( - f'm4b-tool merge --jobs=4 --writer="{author}" ' - f'--albumartist="{author}" --album="{title}" ' - f'--name="{title}" --output-file="{title}-{vol_num}.m4b" ' + f"m4b-tool merge --jobs=4 " + f'--writer="{author}" ' + f'--sortalbum="{title}" ' + f'--albumartist="{author}" ' + f'--album="{title}" ' + f'--name="{title}" ' + f'--series="{title}" ' + f'--series-part="{series_part}" ' + f'--output-file="{title}-{vol_num}.m4b" ' f'"{dirname}" -vvv' ) + lines.append(line) if not lines: @@ -76,7 +89,14 @@ def build_merge_block(title: str, author: str, volumes): # ------------------------------------------------------------ # Main generator # ------------------------------------------------------------ +@logcall def generate_all_scripts(book_base: str, title: str, author: str): + # -------------------------------------------------------- + # Defensive normalize + # -------------------------------------------------------- + title = (title or "").strip() + author = (author or "").strip() + log(f"[SCRIPTGEN] Generating scripts in {book_base}") # Load templates diff --git a/bookscraper/static/css/bookcard.css b/bookscraper/static/css/bookcard.css index ae46acc..84b42c2 100644 --- a/bookscraper/static/css/bookcard.css +++ b/bookscraper/static/css/bookcard.css @@ -1,8 +1,11 @@ /* ======================================================================= File: static/css/bookcard.css Purpose: - All styling for registered book cards (book-card) + - status colors + start/abort buttons + progress bars + Styling voor registered book cards: + - status kleuren + - badges + - start/abort/statuscheck + - progress bars ======================================================================= */ /* ----------------------------------------------------------------------- @@ -17,7 +20,7 @@ } /* ----------------------------------------------------------------------- - BOOK CARD + BOOK CARD BASE ----------------------------------------------------------------------- */ .book-card { @@ -36,34 +39,28 @@ } /* ----------------------------------------------------------------------- - STATUS COLORS + STATUS COLORS (BOOK CARD BORDER) ----------------------------------------------------------------------- */ -.book-card.processing { - border-color: #007aff; - box-shadow: 0 0 6px rgba(0, 122, 255, 0.35); -} - +/* Downloading / actief bezig */ .book-card.downloading { border-color: #ff9500; box-shadow: 0 0 6px rgba(255, 149, 0, 0.35); } -.book-card.parsing { - border-color: #ffcc00; - box-shadow: 0 0 6px rgba(255, 204, 0, 0.35); -} - +/* Audio fase */ .book-card.audio { - border-color: #34c759; - box-shadow: 0 0 6px rgba(52, 199, 89, 0.35); + border-color: #ffca28; + box-shadow: 0 0 6px rgba(255, 202, 40, 0.35); } -.book-card.completed { - border-color: #34c759; - box-shadow: 0 0 6px rgba(52, 199, 89, 0.35); +/* Volledig klaar */ +.book-card.done { + border: 2px solid #4caf50; + box-shadow: 0 0 6px rgba(76, 175, 80, 0.35); } +/* Afgebroken */ .book-card.aborted { border-color: #ff3b30; box-shadow: 0 0 6px rgba(255, 59, 48, 0.35); @@ -188,6 +185,21 @@ background: #555; } +/* Statuscheck */ +.statuscheck-btn { + background-color: #444; + color: #fff; + border: 1px solid #666; + margin-left: 4px; + padding: 4px 8px; + border-radius: 6px; + font-size: 12px; + cursor: pointer; +} +.statuscheck-btn:hover { + background-color: #333; +} + /* ----------------------------------------------------------------------- PROGRESS (FULL WIDTH) ----------------------------------------------------------------------- */ @@ -201,7 +213,7 @@ } .progress-row { - margin-bottom: 2px; + margin-bottom: 4px; } .progress-label { @@ -225,12 +237,12 @@ transition: width 0.4s ease; } -/* Download = blauw */ +/* Download */ .progressbar-fill.download { background: #2196f3; } -/* Audio = groen */ +/* Audio */ .progressbar-fill.audio { background: #4caf50; } @@ -249,9 +261,50 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); pointer-events: none; } -.statuscheck-btn { - background-color: #444; - color: #fff; - border: 1px solid #666; - margin-left: 4px; + +/* ----------------------------------------------------------------------- + STATUS BADGE + ----------------------------------------------------------------------- */ + +.status-badge { + display: inline-block; + margin-bottom: 6px; + padding: 2px 8px; + font-size: 11px; + font-weight: 600; + border-radius: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; + cursor: default; +} + +/* DONE */ +.status-badge.status-done { + background-color: #e6f4ea; + color: #2e7d32; + border: 1px solid #4caf50; +} + +/* AUDIO */ +.status-badge.status-audio { + background-color: #fff8e1; + color: #8d6e00; + border: 1px solid #ffca28; +} + +/* DOWNLOADING */ +.status-badge.status-downloading { + background-color: #e3f2fd; + color: #1565c0; + border: 1px solid #42a5f5; +} + +/* Statuscheck */ +.icon-statuscheck { + background: #444; +} + +.icon-statuscheck:hover { + background: #333; + transform: scale(1.05); } diff --git a/bookscraper/static/js/bookcard_controller.js b/bookscraper/static/js/bookcard_controller.js index 9a8717b..01641e6 100644 --- a/bookscraper/static/js/bookcard_controller.js +++ b/bookscraper/static/js/bookcard_controller.js @@ -52,6 +52,7 @@ function updateSingleBookCard(card, state) { console.log("[BOOKCARD] updateSingleBookCard", state.book_idx); updateStatus(card, state); + updateStatusBadge(card, state); updateButtons(card, state); updateProgress(card, state); } @@ -64,6 +65,21 @@ function updateStatus(card, state) { console.log("[BOOKCARD][STATUS]", state.book_idx, "→", state.status); card.className = `book-card ${state.status || ""}`; } +function updateStatusBadge(card, state) { + const badge = card.querySelector(".status-badge"); + if (!badge) return; + + const status = (state.status || "").toLowerCase(); + + badge.textContent = status.toUpperCase(); + badge.className = `status-badge status-${status}`; + badge.title = + { + downloading: "Bezig met downloaden", + audio: "Downloads compleet, audio wordt gegenereerd", + done: "Alle chapters en audio zijn compleet", + }[status] || ""; +} /* ============================================================ BUTTONS diff --git a/bookscraper/templates/components/book_list_item.html b/bookscraper/templates/components/___book_list_item.html similarity index 100% rename from bookscraper/templates/components/book_list_item.html rename to bookscraper/templates/components/___book_list_item.html diff --git a/bookscraper/templates/components/bookcard.html b/bookscraper/templates/components/bookcard.html index a316bc1..d31c773 100644 --- a/bookscraper/templates/components/bookcard.html +++ b/bookscraper/templates/components/bookcard.html @@ -31,6 +31,20 @@ component) ============================================================ #}
+ + {% if b.status %} + + {{ b.status | upper }} + + {% endif %} +
{{ b.title }}
{{ b.author }}
@@ -62,10 +76,10 @@ component) ============================================================ #} >
diff --git a/bookscraper/templates/dashboard/dashboard.html b/bookscraper/templates/dashboard/dashboard.html index 3b55319..08518bf 100644 --- a/bookscraper/templates/dashboard/dashboard.html +++ b/bookscraper/templates/dashboard/dashboard.html @@ -22,25 +22,11 @@
- {% include "components/registered_books.html" %} - -
-
-

Actieve boeken

- - {% if books and books|length > 0 %} -
- {% for book in books %} {% include "components/book_list_item.html" %} {% - endfor %} -
- {% else %} -
Geen actieve boeken.
- {% endif %} -
+ {% include "components/registered_books.html" %}