feat: implement WebADIS authentication and add medianumber retrieval functionality

This commit is contained in:
2025-10-21 15:26:20 +02:00
parent 0764a6b06a
commit f63bcc8446
12 changed files with 323 additions and 9 deletions

View File

@@ -1,14 +1,133 @@
import os
import time
from concurrent.futures import ThreadPoolExecutor
from queue import Empty, Queue
from PySide6 import QtCore, QtWidgets
from PySide6.QtMultimedia import QAudioOutput, QMediaPlayer
from src.backend.catalogue import Catalogue
from src.backend.database import Database
from src.backend.webadis import get_book_medianr
from src.logic.SRU import SWB
from src.shared.logging import log
from .widget_sources.admin_update_signatures_ui import Ui_Dialog
class MedianrThread(QtCore.QThread):
progress = QtCore.Signal(int)
currtot = QtCore.Signal(int, int)
def __init__(self, books=None, thread_count=6):
super().__init__()
self.books = books or []
self.total = 0
# Database instances are not always thread-safe across threads; create one per worker.
# Keep a shared auth that is safe to read.
db_main = Database()
self.auth = db_main.getWebADISAuth()
if self.auth == ("", ""):
raise ValueError("WebADIS authentication not set in database.")
self.thread_count = max(1, thread_count)
self._stop_requested = False
def run(self):
# Use ThreadPoolExecutor to parallelize I/O-bound tasks.
total_books = len(self.books)
if total_books == 0:
log.debug("MedianrThread: no books to process")
return
chunk_size = (total_books + self.thread_count - 1) // self.thread_count
chunks = [
self.books[i : i + chunk_size] for i in range(0, total_books, chunk_size)
]
# queue for worker results and progress
q = Queue()
def medianrworker(chunk: list[dict]):
# Each worker creates its own Database instance for thread-safety
db = Database()
for book in chunk:
if self._stop_requested:
break
try:
booknr = get_book_medianr(
book["bookdata"].signature,
db.getApparatNrByBookId(book["id"]),
self.auth,
)
log.debug(
f"Book ID {book['id']} - Signature {book['bookdata'].signature} - Medianr {booknr}"
)
book_data = book["bookdata"]
book_data.medianr = booknr
db.updateBookdata(book["id"], book_data)
q.put(("progress", 1))
except Exception as e:
log.error(f"Medianr worker error for book {book}: {e}")
q.put(("progress", 1))
time.sleep(10)
q.put(("done", None))
processed = 0
finished_workers = 0
with ThreadPoolExecutor(max_workers=len(chunks)) as ex:
futures = [ex.submit(medianrworker, ch) for ch in chunks]
log.info(
f"Launched {len(futures)} worker thread(s) for {total_books} entries: {[len(ch) for ch in chunks]} entries per thread."
)
# aggregate progress
while finished_workers < len(chunks):
try:
kind, payload = q.get(timeout=0.1)
except Empty:
continue
if kind == "progress":
processed += int(payload)
self.progress.emit(processed)
# emit currtot with processed and current chunk total as best-effort
self.currtot.emit(processed, total_books)
elif kind == "done":
finished_workers += 1
# ensure final progress reached total_books
self.progress.emit(total_books)
self.currtot.emit(total_books, total_books)
def stop(self):
"""Request the thread to stop early."""
self._stop_requested = True
def process_chunk(self, books_chunk):
# kept for backward compatibility but not used by run(); still usable externally
db = Database()
for index, book in enumerate(books_chunk):
try:
booknr = get_book_medianr(
book["bookdata"].signature,
db.getApparatNrByBookId(book["id"]),
self.auth,
)
log.debug(
f"Book ID {book['id']} - Signature {book['bookdata'].signature} - Medianr {booknr}"
)
book_data = book["bookdata"]
book_data.medianr = booknr
db.updateBookdata(book["id"], book_data)
except Exception as e:
log.error(f"Medianr process_chunk error for book {book}: {e}")
self.progress.emit(index + 1)
self.currtot.emit(self.total + 1, len(books_chunk))
self.total += 1
class UpdaterThread(QtCore.QThread):
progress = QtCore.Signal(int)
currtot = QtCore.Signal(int, int)
@@ -98,6 +217,8 @@ class UpdateSignatures(QtWidgets.QDialog, Ui_Dialog):
self.catalogue = Catalogue()
self.player = QMediaPlayer()
self.audio_output = QAudioOutput()
self.spin_thread_count.setMaximum(os.cpu_count())
self.btn_add_medianr.clicked.connect(self.add_medianr)
def play_sound(self, sound_file: str):
self.player.setAudioOutput(self.audio_output)
@@ -114,6 +235,16 @@ class UpdateSignatures(QtWidgets.QDialog, Ui_Dialog):
self.updater.finished.connect(self.updater.deleteLater)
self.updater.start()
def add_medianr(self):
books = self.db.getAllBooks()
books = [book for book in books if book["bookdata"].medianr is None]
total_books = len(books)
self.progressBar.setMaximum(total_books)
self.medianr_thread = MedianrThread(books, self.spin_thread_count.value())
self.medianr_thread.progress.connect(self.update_progress)
self.medianr_thread.finished.connect(self.medianr_thread.deleteLater)
self.medianr_thread.start()
def add_missing(self):
books = self.db.getAllBooks()
total_books = len(books)