feat: implement WebADIS authentication and add medianumber retrieval functionality
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user