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 logging
|
||||||
import queue as _queue_module
|
import queue as _queue_module
|
||||||
import re
|
import re
|
||||||
|
import select
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from pathlib import Path
|
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 pydantic import BaseModel
|
||||||
|
|
||||||
from musiksammlung.cddb import get_discid, lookup_by_discid
|
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
|
# Zeileneditor für Benutzereingaben
|
||||||
# von _read_line() und _input_or_scan() nutzen dieselbe Queue. Damit gibt
|
#
|
||||||
# es keine verwaisten Threads, die nach einem Foto-Upload weiterhin auf
|
# _read_line(prompt) — nutzt input() auf dem Hauptthread. Durch den Import
|
||||||
# sys.stdin blockieren und nachfolgende Eingaben "stehlen".
|
# 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:
|
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
|
Pfeiltasten, Backspace, Ctrl-A/E, History etc. funktionieren,
|
||||||
Threads auf stdin warten.
|
weil input() den GNU-readline-Editor nutzt (sofern importiert).
|
||||||
"""
|
"""
|
||||||
_ensure_stdin_reader()
|
try:
|
||||||
if prompt:
|
return input(prompt)
|
||||||
print(prompt, end="", flush=True)
|
except EOFError:
|
||||||
return _stdin_queue.get()
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _input_or_scan(
|
def _input_or_scan(
|
||||||
prompt: str,
|
prompt: str,
|
||||||
scanner: ScannerServer | None,
|
scanner: ScannerServer | None,
|
||||||
) -> tuple[str, Path | 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:
|
Returns:
|
||||||
(eingegebener Text, None) — wenn der User Enter drückt
|
(eingegebener Text, None) — wenn der User Enter drückt
|
||||||
|
|
@ -206,7 +193,6 @@ def _input_or_scan(
|
||||||
if scanner is None:
|
if scanner is None:
|
||||||
return _clean_input(_read_line(prompt)), None
|
return _clean_input(_read_line(prompt)), None
|
||||||
|
|
||||||
_ensure_stdin_reader()
|
|
||||||
print(prompt, end="", flush=True)
|
print(prompt, end="", flush=True)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -215,11 +201,10 @@ def _input_or_scan(
|
||||||
print("\n [Foto empfangen — weiter automatisch]", flush=True)
|
print("\n [Foto empfangen — weiter automatisch]", flush=True)
|
||||||
return "", photo
|
return "", photo
|
||||||
|
|
||||||
try:
|
ready, _, _ = select.select([sys.stdin], [], [], 0.1)
|
||||||
val = _stdin_queue.get(timeout=0.1)
|
if ready:
|
||||||
return _clean_input(val), None
|
line = sys.stdin.readline().rstrip("\n")
|
||||||
except _queue_module.Empty:
|
return _clean_input(line), None
|
||||||
continue
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue