Replace shared stdin reader with readline + select for comfortable line editing
Use GNU readline (arrow keys, backspace, history, Ctrl-A/E) for all user prompts via input(). Replace the shared reader thread with select()-based non-blocking polling in _input_or_scan() — eliminates dangling threads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8c25bc65be
commit
fd8de16bdd
1 changed files with 31 additions and 46 deletions
|
|
@ -5,11 +5,17 @@ from __future__ import annotations
|
|||
import logging
|
||||
import queue as _queue_module
|
||||
import re
|
||||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import readline # noqa: F401 — aktiviert Zeileneditor für input()
|
||||
except ImportError:
|
||||
pass # Fallback: input() ohne Zeileneditor
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from musiksammlung.cddb import get_discid, lookup_by_discid
|
||||
|
|
@ -145,59 +151,40 @@ def _get_vision_result(
|
|||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared stdin reader — ein einziger Thread liest von stdin, alle Aufrufe
|
||||
# von _read_line() und _input_or_scan() nutzen dieselbe Queue. Damit gibt
|
||||
# es keine verwaisten Threads, die nach einem Foto-Upload weiterhin auf
|
||||
# sys.stdin blockieren und nachfolgende Eingaben "stehlen".
|
||||
# Zeileneditor für Benutzereingaben
|
||||
#
|
||||
# _read_line(prompt) — nutzt input() auf dem Hauptthread. Durch den Import
|
||||
# von readline oben stehen Pfeiltasten, Backspace, Ctrl-A/E, History
|
||||
# (Pfeil hoch/runter) usw. automatisch zur Verfügung.
|
||||
#
|
||||
# _input_or_scan(prompt, scanner) — pollt stdin UND Scanner-Queue parallel
|
||||
# via select() ohne Background-Thread. Kein readline (nur terminal-
|
||||
# eigenes Editing: Backspace, Ctrl-U), dafür keine verwaisten Threads.
|
||||
# Betrifft nur EAN-Prompt und Disc-Insert — dort ist Komfort-Editing
|
||||
# nicht nötig (Enter / kurze Ziffern).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_stdin_queue: _queue_module.Queue[str] = _queue_module.Queue()
|
||||
_stdin_reader_lock = threading.Lock()
|
||||
_stdin_reader_started = False
|
||||
|
||||
|
||||
def _ensure_stdin_reader() -> None:
|
||||
"""Startet den gemeinsamen stdin-Reader-Thread (einmalig, idempotent)."""
|
||||
global _stdin_reader_started
|
||||
with _stdin_reader_lock:
|
||||
if _stdin_reader_started:
|
||||
return
|
||||
_stdin_reader_started = True
|
||||
|
||||
def _reader() -> None:
|
||||
while True:
|
||||
try:
|
||||
line = sys.stdin.readline()
|
||||
if not line: # EOF
|
||||
_stdin_queue.put("")
|
||||
break
|
||||
_stdin_queue.put(line.rstrip("\n"))
|
||||
except (EOFError, OSError):
|
||||
_stdin_queue.put("")
|
||||
break
|
||||
|
||||
threading.Thread(target=_reader, daemon=True).start()
|
||||
|
||||
|
||||
def _read_line(prompt: str = "") -> str:
|
||||
"""Liest eine Zeile von stdin über den Shared Reader.
|
||||
"""Liest eine Zeile von stdin mit readline-Unterstützung.
|
||||
|
||||
Ersetzt input() überall in interactive_rip, damit keine konkurrierenden
|
||||
Threads auf stdin warten.
|
||||
Pfeiltasten, Backspace, Ctrl-A/E, History etc. funktionieren,
|
||||
weil input() den GNU-readline-Editor nutzt (sofern importiert).
|
||||
"""
|
||||
_ensure_stdin_reader()
|
||||
if prompt:
|
||||
print(prompt, end="", flush=True)
|
||||
return _stdin_queue.get()
|
||||
try:
|
||||
return input(prompt)
|
||||
except EOFError:
|
||||
return ""
|
||||
|
||||
|
||||
def _input_or_scan(
|
||||
prompt: str,
|
||||
scanner: ScannerServer | None,
|
||||
) -> tuple[str, Path | None]:
|
||||
"""Kombiniertes stdin + Scanner-Queue: wartet gleichzeitig auf Tastatur und Foto.
|
||||
"""Wartet gleichzeitig auf Tastatureingabe und Foto-Upload.
|
||||
|
||||
Nutzt den Shared stdin-Reader — kein eigener Thread pro Aufruf.
|
||||
Nutzt select() zum nicht-blockierenden Polling — kein Background-Thread,
|
||||
keine verwaisten Threads nach Foto-Upload.
|
||||
|
||||
Returns:
|
||||
(eingegebener Text, None) — wenn der User Enter drückt
|
||||
|
|
@ -206,7 +193,6 @@ def _input_or_scan(
|
|||
if scanner is None:
|
||||
return _clean_input(_read_line(prompt)), None
|
||||
|
||||
_ensure_stdin_reader()
|
||||
print(prompt, end="", flush=True)
|
||||
|
||||
while True:
|
||||
|
|
@ -215,11 +201,10 @@ def _input_or_scan(
|
|||
print("\n [Foto empfangen — weiter automatisch]", flush=True)
|
||||
return "", photo
|
||||
|
||||
try:
|
||||
val = _stdin_queue.get(timeout=0.1)
|
||||
return _clean_input(val), None
|
||||
except _queue_module.Empty:
|
||||
continue
|
||||
ready, _, _ = select.select([sys.stdin], [], [], 0.1)
|
||||
if ready:
|
||||
line = sys.stdin.readline().rstrip("\n")
|
||||
return _clean_input(line), None
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue