272 lines
10 KiB
Python
272 lines
10 KiB
Python
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)
|
|
|
|
def __init__(self, books=None):
|
|
super().__init__()
|
|
self.books = books
|
|
self.db = Database()
|
|
self.catalogue = Catalogue()
|
|
|
|
def run(self):
|
|
total_books = len(self.books)
|
|
for index, book in enumerate(self.books):
|
|
try:
|
|
id = book["id"]
|
|
bookdata = book["bookdata"]
|
|
ppn = bookdata.ppn
|
|
result = self.catalogue.get_book(ppn)
|
|
if result:
|
|
log.debug(f"Updating book {id} with ppn {ppn}")
|
|
bookdata.signature = result.signature
|
|
# #print(bookdata)
|
|
self.db.updateBookdata(id, bookdata)
|
|
else:
|
|
log.debug(f"No result for {ppn}")
|
|
# #print(f"No result for {ppn}")
|
|
# self.db.deleteBook(id)
|
|
except Exception as e:
|
|
log.error(f"Error updating book {book}: {e}")
|
|
self.progress.emit(index + 1)
|
|
self.currtot.emit(index + 1, total_books)
|
|
|
|
|
|
class CompleterThread(QtCore.QThread):
|
|
progress = QtCore.Signal(int)
|
|
currtot = QtCore.Signal(int, int)
|
|
|
|
def __init__(self, books=None):
|
|
super().__init__()
|
|
self.books = books
|
|
self.db = Database()
|
|
self.catalogue = Catalogue()
|
|
self.swb = SWB()
|
|
|
|
def run(self):
|
|
total_books = len(self.books)
|
|
for index, book in enumerate(self.books):
|
|
try:
|
|
id = book["id"]
|
|
bookdata = book["bookdata"]
|
|
|
|
ppn = bookdata.ppn
|
|
cat_book = self.catalogue.get_book(f"kid:{ppn}")
|
|
|
|
swb_version = self.swb.getBooks(["pica.bib=20735", f"pica.ppn={ppn}"])[
|
|
0
|
|
]
|
|
if cat_book:
|
|
merged = cat_book.merge(swb_version)
|
|
else:
|
|
merged = swb_version
|
|
|
|
# compare original_book with merged, if different, update db
|
|
if bookdata != merged:
|
|
# #print(f"Updating book {id} with ppn {ppn}")
|
|
# #print("Original book:", bookdata)
|
|
# #print("Merged book:", merged)
|
|
self.db.updateBookdata(id, merged)
|
|
except Exception as e:
|
|
log.error(f"Error updating book {book}: {e}")
|
|
# else:
|
|
# #print(f"No result for {ppn}")
|
|
# self.db.deleteBook(id)
|
|
self.progress.emit(index + 1)
|
|
self.currtot.emit(index + 1, total_books)
|
|
|
|
|
|
class UpdateSignatures(QtWidgets.QDialog, Ui_Dialog):
|
|
def __init__(self, parent=None):
|
|
super(UpdateSignatures, self).__init__(parent)
|
|
self.setupUi(self)
|
|
self.setWindowTitle("Updating signatures...")
|
|
self.progressBar.setValue(0)
|
|
self.btn_update_signatures.clicked.connect(self.update_signatures)
|
|
self.btn_add_missing_data.clicked.connect(self.add_missing)
|
|
self.db = Database()
|
|
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)
|
|
self.audio_output.setVolume(50)
|
|
self.player.setSource(QtCore.QUrl.fromLocalFile(f"src/sounds/{sound_file}"))
|
|
self.player.play()
|
|
|
|
def update_signatures(self):
|
|
books = self.db.getAllBooks()
|
|
total_books = len(books)
|
|
self.progressBar.setMaximum(total_books)
|
|
self.updater = UpdaterThread(books)
|
|
self.updater.progress.connect(self.update_progress)
|
|
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)
|
|
self.progressBar.setMaximum(total_books)
|
|
incomplete_books = [
|
|
book
|
|
for book in books
|
|
if any(
|
|
value in (None, "", "None")
|
|
for value in book["bookdata"].__dict__.values()
|
|
)
|
|
]
|
|
self.completionist = CompleterThread(incomplete_books)
|
|
self.completionist.progress.connect(self.update_progress)
|
|
self.completionist.finished.connect(self.completionist.deleteLater)
|
|
self.completionist.start()
|
|
|
|
def update_progress(self, value):
|
|
self.progressBar.setValue(value)
|
|
if value <= self.progressBar.maximum():
|
|
self.btn_update_signatures.setEnabled(False)
|
|
if value == self.progressBar.maximum():
|
|
self.btn_update_signatures.setEnabled(True)
|
|
self.play_sound("ding.mp3")
|