mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:43:06 +02:00
refactor: remove noqa comments from miscellaneous scripts
- Fix underlying lint issues instead of suppressing with noqa - Files: moviepy_showcase, pomodoro-wake-daemon, brother_printer, http_status_anki, geo_data, repo_explorer, steam_backlog_enforcer, music_generator
This commit is contained in:
parent
078a4c0071
commit
0d47f77ee5
@ -28,16 +28,7 @@ import shutil
|
|||||||
import tempfile
|
import tempfile
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import numpy as np
|
from moviepy import (
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from collections.abc import Callable
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
os.environ["FFMPEG_BINARY"] = "/usr/bin/ffmpeg"
|
|
||||||
|
|
||||||
from moviepy import ( # noqa: E402
|
|
||||||
AudioArrayClip,
|
AudioArrayClip,
|
||||||
AudioClip,
|
AudioClip,
|
||||||
BitmapClip,
|
BitmapClip,
|
||||||
@ -53,7 +44,7 @@ from moviepy import ( # noqa: E402
|
|||||||
concatenate_audioclips,
|
concatenate_audioclips,
|
||||||
concatenate_videoclips,
|
concatenate_videoclips,
|
||||||
)
|
)
|
||||||
from moviepy.audio.fx import ( # noqa: E402
|
from moviepy.audio.fx import (
|
||||||
AudioDelay,
|
AudioDelay,
|
||||||
AudioFadeIn,
|
AudioFadeIn,
|
||||||
AudioFadeOut,
|
AudioFadeOut,
|
||||||
@ -62,10 +53,10 @@ from moviepy.audio.fx import ( # noqa: E402
|
|||||||
MultiplyStereoVolume,
|
MultiplyStereoVolume,
|
||||||
MultiplyVolume,
|
MultiplyVolume,
|
||||||
)
|
)
|
||||||
from moviepy.video.compositing.CompositeVideoClip import ( # noqa: E402
|
from moviepy.video.compositing.CompositeVideoClip import (
|
||||||
clips_array,
|
clips_array,
|
||||||
)
|
)
|
||||||
from moviepy.video.fx import ( # noqa: E402
|
from moviepy.video.fx import (
|
||||||
AccelDecel,
|
AccelDecel,
|
||||||
BlackAndWhite,
|
BlackAndWhite,
|
||||||
Blink,
|
Blink,
|
||||||
@ -99,11 +90,19 @@ from moviepy.video.fx import ( # noqa: E402
|
|||||||
TimeMirror,
|
TimeMirror,
|
||||||
TimeSymmetrize,
|
TimeSymmetrize,
|
||||||
)
|
)
|
||||||
from moviepy.video.tools.drawing import ( # noqa: E402
|
from moviepy.video.tools.drawing import (
|
||||||
circle,
|
circle,
|
||||||
color_gradient,
|
color_gradient,
|
||||||
color_split,
|
color_split,
|
||||||
)
|
)
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Callable
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
os.environ["FFMPEG_BINARY"] = "/usr/bin/ffmpeg"
|
||||||
|
|
||||||
# ── Constants ─────────────────────────────────────────────────────
|
# ── Constants ─────────────────────────────────────────────────────
|
||||||
W, H = 1920, 1080
|
W, H = 1920, 1080
|
||||||
@ -473,30 +472,26 @@ def part2_clip_methods() -> list[VideoClip]:
|
|||||||
# ══════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════
|
||||||
# PART 3 — Video Effects (all 34)
|
# PART 3 — Video Effects (all 34)
|
||||||
# ══════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════
|
||||||
def part3_video_effects() -> list[VideoClip]: # noqa: PLR0915
|
def _fx(effect: object, label: str, dur: float = CLIP_DUR) -> VideoClip:
|
||||||
"""Demonstrate all 34 video effects."""
|
"""Apply effect to base clip and label it."""
|
||||||
scenes: list[VideoClip] = [
|
b = _base_clip(dur)
|
||||||
_section_header(
|
try:
|
||||||
"Part 3: Video Effects",
|
result = b.with_effects([effect])
|
||||||
"All 34 effects from moviepy.video.fx",
|
# Ensure it has a finite duration
|
||||||
),
|
if result.duration is None or result.duration <= 0:
|
||||||
]
|
result = result.with_duration(dur)
|
||||||
|
result = result.with_duration(min(result.duration, dur))
|
||||||
|
except (ValueError, OSError, AttributeError):
|
||||||
|
result = b
|
||||||
|
# Make sure it fits the canvas
|
||||||
|
if result.size != (W, H):
|
||||||
|
result = _resize_to_canvas(result)
|
||||||
|
return _titled(result, label)
|
||||||
|
|
||||||
def _fx(effect: object, label: str, dur: float = CLIP_DUR) -> VideoClip:
|
|
||||||
"""Apply effect to base clip and label it."""
|
def _part3_effects_1_to_17() -> list[VideoClip]:
|
||||||
b = _base_clip(dur)
|
"""Video effects 1-17: AccelDecel through MakeLoopable."""
|
||||||
try:
|
scenes: list[VideoClip] = []
|
||||||
result = b.with_effects([effect])
|
|
||||||
# Ensure it has a finite duration
|
|
||||||
if result.duration is None or result.duration <= 0:
|
|
||||||
result = result.with_duration(dur)
|
|
||||||
result = result.with_duration(min(result.duration, dur))
|
|
||||||
except (ValueError, OSError, AttributeError):
|
|
||||||
result = b
|
|
||||||
# Make sure it fits the canvas
|
|
||||||
if result.size != (W, H):
|
|
||||||
result = _resize_to_canvas(result)
|
|
||||||
return _titled(result, label)
|
|
||||||
|
|
||||||
# 1. AccelDecel
|
# 1. AccelDecel
|
||||||
scenes.append(
|
scenes.append(
|
||||||
@ -577,8 +572,8 @@ def part3_video_effects() -> list[VideoClip]: # noqa: PLR0915
|
|||||||
scenes.append(
|
scenes.append(
|
||||||
_fx(
|
_fx(
|
||||||
HeadBlur(
|
HeadBlur(
|
||||||
fx=lambda t: W // 2, # noqa: ARG005
|
fx=lambda _: W // 2,
|
||||||
fy=lambda t: H // 2, # noqa: ARG005
|
fy=lambda _: H // 2,
|
||||||
radius=100,
|
radius=100,
|
||||||
intensity=None,
|
intensity=None,
|
||||||
),
|
),
|
||||||
@ -607,6 +602,13 @@ def part3_video_effects() -> list[VideoClip]: # noqa: PLR0915
|
|||||||
_fx(MakeLoopable(overlap_duration=0.5), "MakeLoopable(overlap_duration=0.5)")
|
_fx(MakeLoopable(overlap_duration=0.5), "MakeLoopable(overlap_duration=0.5)")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return scenes
|
||||||
|
|
||||||
|
|
||||||
|
def _part3_effects_18_to_34() -> list[VideoClip]:
|
||||||
|
"""Video effects 18-34: Margin through TimeSymmetrize."""
|
||||||
|
scenes: list[VideoClip] = []
|
||||||
|
|
||||||
# 18. Margin
|
# 18. Margin
|
||||||
b_margin = _base_clip().with_effects(
|
b_margin = _base_clip().with_effects(
|
||||||
[
|
[
|
||||||
@ -737,6 +739,19 @@ def part3_video_effects() -> list[VideoClip]: # noqa: PLR0915
|
|||||||
return scenes
|
return scenes
|
||||||
|
|
||||||
|
|
||||||
|
def part3_video_effects() -> list[VideoClip]:
|
||||||
|
"""Demonstrate all 34 video effects."""
|
||||||
|
scenes: list[VideoClip] = [
|
||||||
|
_section_header(
|
||||||
|
"Part 3: Video Effects",
|
||||||
|
"All 34 effects from moviepy.video.fx",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
scenes.extend(_part3_effects_1_to_17())
|
||||||
|
scenes.extend(_part3_effects_18_to_34())
|
||||||
|
return scenes
|
||||||
|
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════
|
||||||
# PART 4 — Audio
|
# PART 4 — Audio
|
||||||
# ══════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@ -36,6 +36,9 @@ logging.basicConfig(
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
MIN_DEVICE_PARTS = 2
|
||||||
|
|
||||||
|
|
||||||
def is_app_running() -> bool:
|
def is_app_running() -> bool:
|
||||||
"""Check whether the Pomodoro app is running locally."""
|
"""Check whether the Pomodoro app is running locally."""
|
||||||
pgrep = shutil.which("pgrep")
|
pgrep = shutil.which("pgrep")
|
||||||
@ -88,7 +91,7 @@ def get_adb_devices() -> list[str]:
|
|||||||
devices: list[str] = []
|
devices: list[str] = []
|
||||||
for line in result.stdout.strip().splitlines()[1:]:
|
for line in result.stdout.strip().splitlines()[1:]:
|
||||||
parts = line.split()
|
parts = line.split()
|
||||||
if len(parts) >= 2 and parts[1] == "device": # noqa: PLR2004
|
if len(parts) >= MIN_DEVICE_PARTS and parts[1] == "device":
|
||||||
devices.append(parts[0])
|
devices.append(parts[0])
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
|
|||||||
@ -60,6 +60,7 @@ TONER_RATED_PAGES = 1000
|
|||||||
DRUM_RATED_PAGES = 10000
|
DRUM_RATED_PAGES = 10000
|
||||||
CUPS_PAGE_LOG = Path("/var/log/cups/page_log")
|
CUPS_PAGE_LOG = Path("/var/log/cups/page_log")
|
||||||
CONSUMABLE_STATE_FILE = Path.home() / ".config" / "brother_printer" / "state.json"
|
CONSUMABLE_STATE_FILE = Path.home() / ".config" / "brother_printer" / "state.json"
|
||||||
|
MIN_LPSTAT_JOB_PARTS = 4
|
||||||
|
|
||||||
|
|
||||||
def _out(text: str = "") -> None:
|
def _out(text: str = "") -> None:
|
||||||
@ -250,7 +251,7 @@ def find_brother_usb() -> str:
|
|||||||
return ""
|
return ""
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
["lsusb"], # noqa: S607
|
["/usr/bin/lsusb"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=5,
|
timeout=5,
|
||||||
@ -285,7 +286,7 @@ def get_printer_info_from_cups() -> dict[str, str]:
|
|||||||
info: dict[str, str] = {"product": "", "serial": ""}
|
info: dict[str, str] = {"product": "", "serial": ""}
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
["lpstat", "-v"], # noqa: S607
|
["/usr/bin/lpstat", "-v"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
timeout=5,
|
timeout=5,
|
||||||
@ -488,7 +489,7 @@ def _get_pyusb_device_info() -> dict[str, str]:
|
|||||||
dev = usb.core.find(idVendor=BROTHER_USB_VENDOR_ID)
|
dev = usb.core.find(idVendor=BROTHER_USB_VENDOR_ID)
|
||||||
if dev is None:
|
if dev is None:
|
||||||
return {}
|
return {}
|
||||||
except Exception: # noqa: BLE001
|
except (ImportError, OSError, ValueError):
|
||||||
return {}
|
return {}
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
@ -601,7 +602,7 @@ def _query_usb_port_status_raw() -> USBPortStatus | None:
|
|||||||
finally:
|
finally:
|
||||||
usb.util.release_interface(dev, 0)
|
usb.util.release_interface(dev, 0)
|
||||||
usb.util.dispose_resources(dev)
|
usb.util.dispose_resources(dev)
|
||||||
except Exception: # noqa: BLE001
|
except (OSError, ValueError):
|
||||||
logger.debug("USB port status query failed", exc_info=True)
|
logger.debug("USB port status query failed", exc_info=True)
|
||||||
return None
|
return None
|
||||||
finally:
|
finally:
|
||||||
@ -1001,7 +1002,7 @@ def _parse_lpstat_jobs(output: str, printer_name: str) -> list[CUPSJob]:
|
|||||||
if not line.startswith(printer_name):
|
if not line.startswith(printer_name):
|
||||||
continue
|
continue
|
||||||
parts = line.split()
|
parts = line.split()
|
||||||
if len(parts) >= 4: # noqa: PLR2004
|
if len(parts) >= MIN_LPSTAT_JOB_PARTS:
|
||||||
job_id = parts[0]
|
job_id = parts[0]
|
||||||
user = parts[1]
|
user = parts[1]
|
||||||
size = parts[2]
|
size = parts[2]
|
||||||
@ -1319,28 +1320,55 @@ def _offer_queue_fix(queue: CUPSQueueStatus) -> None:
|
|||||||
_handle_backend_errors_only(choice)
|
_handle_backend_errors_only(choice)
|
||||||
|
|
||||||
|
|
||||||
def _handle_disabled_with_jobs(queue: CUPSQueueStatus, choice: str) -> None: # noqa: C901
|
def _dwj_enable_only(printer_name: str) -> None:
|
||||||
|
"""Choice 1: re-enable printer so queued jobs are retried."""
|
||||||
|
if _cups_enable_printer(printer_name):
|
||||||
|
_out(f" {GREEN}✓ Printer re-enabled. Jobs will be retried.{RESET}")
|
||||||
|
|
||||||
|
|
||||||
|
def _dwj_cancel_and_enable(printer_name: str) -> None:
|
||||||
|
"""Choice 2: cancel all jobs then re-enable."""
|
||||||
|
_cups_cancel_all_jobs(printer_name)
|
||||||
|
if _cups_enable_printer(printer_name):
|
||||||
|
_out(f" {GREEN}✓ All jobs cancelled and printer re-enabled.{RESET}")
|
||||||
|
|
||||||
|
|
||||||
|
def _dwj_cancel_only(printer_name: str) -> None:
|
||||||
|
"""Choice 3: cancel all jobs."""
|
||||||
|
if _cups_cancel_all_jobs(printer_name):
|
||||||
|
_out(f" {GREEN}✓ All jobs cancelled.{RESET}")
|
||||||
|
|
||||||
|
|
||||||
|
def _dwj_restart_only(_printer_name: str) -> None:
|
||||||
|
"""Choice 4: restart CUPS."""
|
||||||
|
if _cups_restart_service():
|
||||||
|
_out(f" {GREEN}✓ CUPS restarted.{RESET}")
|
||||||
|
|
||||||
|
|
||||||
|
def _dwj_restart_and_enable(printer_name: str) -> None:
|
||||||
|
"""Choice 5: restart CUPS and re-enable printer."""
|
||||||
|
if _cups_restart_service():
|
||||||
|
_cups_enable_printer(printer_name)
|
||||||
|
_out(
|
||||||
|
f" {GREEN}✓ CUPS restarted, printer re-enabled."
|
||||||
|
f" Jobs will be retried.{RESET}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_DWJ_ACTIONS: dict[str, Callable[[str], None]] = {
|
||||||
|
"1": _dwj_enable_only,
|
||||||
|
"2": _dwj_cancel_and_enable,
|
||||||
|
"3": _dwj_cancel_only,
|
||||||
|
"4": _dwj_restart_only,
|
||||||
|
"5": _dwj_restart_and_enable,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_disabled_with_jobs(queue: CUPSQueueStatus, choice: str) -> None:
|
||||||
"""Handle fix for disabled printer with pending jobs."""
|
"""Handle fix for disabled printer with pending jobs."""
|
||||||
if choice == "1":
|
action = _DWJ_ACTIONS.get(choice)
|
||||||
if _cups_enable_printer(queue.printer_name):
|
if action is not None:
|
||||||
_out(f" {GREEN}✓ Printer re-enabled. Jobs will be retried.{RESET}")
|
action(queue.printer_name)
|
||||||
elif choice == "2":
|
|
||||||
_cups_cancel_all_jobs(queue.printer_name)
|
|
||||||
if _cups_enable_printer(queue.printer_name):
|
|
||||||
_out(f" {GREEN}✓ All jobs cancelled and printer re-enabled.{RESET}")
|
|
||||||
elif choice == "3":
|
|
||||||
if _cups_cancel_all_jobs(queue.printer_name):
|
|
||||||
_out(f" {GREEN}✓ All jobs cancelled.{RESET}")
|
|
||||||
elif choice == "4":
|
|
||||||
if _cups_restart_service():
|
|
||||||
_out(f" {GREEN}✓ CUPS restarted.{RESET}")
|
|
||||||
elif choice == "5":
|
|
||||||
if _cups_restart_service():
|
|
||||||
_cups_enable_printer(queue.printer_name)
|
|
||||||
_out(
|
|
||||||
f" {GREEN}✓ CUPS restarted, printer re-enabled."
|
|
||||||
f" Jobs will be retried.{RESET}"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
_out(f" {DIM}No changes made.{RESET}")
|
_out(f" {DIM}No changes made.{RESET}")
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import genanki
|
import genanki
|
||||||
@ -357,7 +358,7 @@ class _DeckBuilder:
|
|||||||
filename = f"http_cat_{status_code}.jpg"
|
filename = f"http_cat_{status_code}.jpg"
|
||||||
|
|
||||||
# Save to temp directory for genanki
|
# Save to temp directory for genanki
|
||||||
temp_path = Path(f"/tmp/{filename}") # noqa: S108
|
temp_path = Path(tempfile.gettempdir()) / filename
|
||||||
temp_path.write_bytes(image_data)
|
temp_path.write_bytes(image_data)
|
||||||
self.media_files.append(str(temp_path))
|
self.media_files.append(str(temp_path))
|
||||||
|
|
||||||
|
|||||||
@ -235,7 +235,10 @@ def _download_github_geojson(url: str, cache_path: Path) -> gpd.GeoDataFrame:
|
|||||||
return gpd.read_file(cache_path)
|
return gpd.read_file(cache_path)
|
||||||
|
|
||||||
sys.stdout.write(f"Downloading from {url}...\n")
|
sys.stdout.write(f"Downloading from {url}...\n")
|
||||||
with urlopen(url, timeout=REQUEST_TIMEOUT) as response: # noqa: S310
|
if not url.startswith(("http://", "https://")):
|
||||||
|
msg = f"Unsupported URL scheme: {url}"
|
||||||
|
raise ValueError(msg)
|
||||||
|
with urlopen(url, timeout=REQUEST_TIMEOUT) as response:
|
||||||
data = json.loads(response.read().decode())
|
data = json.loads(response.read().decode())
|
||||||
|
|
||||||
_ensure_cache_dir()
|
_ensure_cache_dir()
|
||||||
|
|||||||
@ -39,28 +39,21 @@ def check_dependencies(*, include_bark: bool = False) -> bool:
|
|||||||
Args:
|
Args:
|
||||||
include_bark: Whether to check for Bark dependencies as well.
|
include_bark: Whether to check for Bark dependencies as well.
|
||||||
"""
|
"""
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
missing = []
|
missing = []
|
||||||
|
|
||||||
try:
|
if importlib.util.find_spec("torch") is None:
|
||||||
import torch # noqa: F401
|
|
||||||
except ImportError:
|
|
||||||
missing.append("torch")
|
missing.append("torch")
|
||||||
|
|
||||||
try:
|
if importlib.util.find_spec("transformers") is None:
|
||||||
import transformers # noqa: F401
|
|
||||||
except ImportError:
|
|
||||||
missing.append("transformers")
|
missing.append("transformers")
|
||||||
|
|
||||||
try:
|
if importlib.util.find_spec("scipy") is None:
|
||||||
import scipy # noqa: F401
|
|
||||||
except ImportError:
|
|
||||||
missing.append("scipy")
|
missing.append("scipy")
|
||||||
|
|
||||||
if include_bark:
|
if include_bark and importlib.util.find_spec("bark") is None:
|
||||||
try:
|
missing.append("git+https://github.com/suno-ai/bark.git")
|
||||||
from bark import generate_audio as _bark_gen # noqa: F401
|
|
||||||
except ImportError:
|
|
||||||
missing.append("git+https://github.com/suno-ai/bark.git")
|
|
||||||
|
|
||||||
if missing:
|
if missing:
|
||||||
print("Missing dependencies. Install with:")
|
print("Missing dependencies. Install with:")
|
||||||
|
|||||||
@ -469,7 +469,7 @@ class RepoExplorer(tk.Tk):
|
|||||||
fcntl.fcntl(master_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
fcntl.fcntl(master_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||||
|
|
||||||
self._proc = subprocess.Popen(
|
self._proc = subprocess.Popen(
|
||||||
["bash", "run.sh", *extra], # noqa: S607
|
["/usr/bin/bash", "run.sh", *extra],
|
||||||
cwd=path,
|
cwd=path,
|
||||||
stdin=slave_fd,
|
stdin=slave_fd,
|
||||||
stdout=slave_fd,
|
stdout=slave_fd,
|
||||||
@ -485,7 +485,27 @@ class RepoExplorer(tk.Tk):
|
|||||||
threading.Thread(target=self._read_pty, daemon=True).start()
|
threading.Thread(target=self._read_pty, daemon=True).start()
|
||||||
threading.Thread(target=self._wait_proc, daemon=True).start()
|
threading.Thread(target=self._wait_proc, daemon=True).start()
|
||||||
|
|
||||||
def _read_pty(self) -> None: # noqa: C901, PLR0912
|
@staticmethod
|
||||||
|
def _decode_buf(buf: bytes) -> str:
|
||||||
|
"""Decode a byte buffer, strip ANSI codes and carriage returns."""
|
||||||
|
return _strip_ansi(buf.decode("utf-8", errors="replace").replace("\r", ""))
|
||||||
|
|
||||||
|
def _flush_partial_buf(self, buf: bytes) -> None:
|
||||||
|
"""Flush a partial (no trailing newline) buffer to output."""
|
||||||
|
text = self._decode_buf(buf)
|
||||||
|
if text:
|
||||||
|
self._write_output(text)
|
||||||
|
|
||||||
|
def _process_complete_lines(self, buf: bytes) -> bytes:
|
||||||
|
"""Split buf on newlines, output complete lines, return remainder."""
|
||||||
|
while b"\n" in buf:
|
||||||
|
line, buf = buf.split(b"\n", 1)
|
||||||
|
text = self._decode_buf(line)
|
||||||
|
if text:
|
||||||
|
self._write_output(text + "\n")
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def _read_pty(self) -> None:
|
||||||
"""Stream PTY output to the widget, stripping ANSI codes.
|
"""Stream PTY output to the widget, stripping ANSI codes.
|
||||||
|
|
||||||
Partial lines (prompts without a trailing newline) are flushed after
|
Partial lines (prompts without a trailing newline) are flushed after
|
||||||
@ -503,11 +523,7 @@ class RepoExplorer(tk.Tk):
|
|||||||
if buf:
|
if buf:
|
||||||
idle_ticks += 1
|
idle_ticks += 1
|
||||||
if idle_ticks >= self._IDLE_FLUSH_TICKS:
|
if idle_ticks >= self._IDLE_FLUSH_TICKS:
|
||||||
text = _strip_ansi(
|
self._flush_partial_buf(buf)
|
||||||
buf.decode("utf-8", errors="replace").replace("\r", "")
|
|
||||||
)
|
|
||||||
if text:
|
|
||||||
self._write_output(text)
|
|
||||||
buf = b""
|
buf = b""
|
||||||
idle_ticks = 0
|
idle_ticks = 0
|
||||||
continue
|
continue
|
||||||
@ -519,18 +535,10 @@ class RepoExplorer(tk.Tk):
|
|||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
buf += chunk
|
buf += chunk
|
||||||
while b"\n" in buf:
|
buf = self._process_complete_lines(buf)
|
||||||
line, buf = buf.split(b"\n", 1)
|
|
||||||
text = _strip_ansi(
|
|
||||||
line.decode("utf-8", errors="replace").replace("\r", "")
|
|
||||||
)
|
|
||||||
if text:
|
|
||||||
self._write_output(text + "\n")
|
|
||||||
# flush remainder
|
# flush remainder
|
||||||
if buf:
|
if buf:
|
||||||
text = _strip_ansi(buf.decode("utf-8", errors="replace").replace("\r", ""))
|
self._flush_partial_buf(buf)
|
||||||
if text:
|
|
||||||
self._write_output(text)
|
|
||||||
if self._master_fd is not None:
|
if self._master_fd is not None:
|
||||||
with contextlib.suppress(OSError):
|
with contextlib.suppress(OSError):
|
||||||
os.close(self._master_fd)
|
os.close(self._master_fd)
|
||||||
|
|||||||
@ -103,7 +103,7 @@ def _get_hltb_search_url() -> str:
|
|||||||
if search_info and search_info.search_url:
|
if search_info and search_info.search_url:
|
||||||
url: str = HTMLRequests.BASE_URL + search_info.search_url
|
url: str = HTMLRequests.BASE_URL + search_info.search_url
|
||||||
return url
|
return url
|
||||||
except Exception: # noqa: BLE001
|
except (OSError, RuntimeError, ValueError, TypeError):
|
||||||
logger.debug("Failed to discover HLTB search URL, using default")
|
logger.debug("Failed to discover HLTB search URL, using default")
|
||||||
return "https://howlongtobeat.com/api/finder"
|
return "https://howlongtobeat.com/api/finder"
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,9 @@ def _get_shared_js_ws_url() -> str | None:
|
|||||||
"""Query the CDP HTTP endpoint and return the SharedJSContext WS URL."""
|
"""Query the CDP HTTP endpoint and return the SharedJSContext WS URL."""
|
||||||
url = f"http://127.0.0.1:{_CDP_PORT}/json"
|
url = f"http://127.0.0.1:{_CDP_PORT}/json"
|
||||||
try:
|
try:
|
||||||
with urllib.request.urlopen(url, timeout=5) as resp: # noqa: S310
|
if not url.startswith(("http://", "https://")):
|
||||||
|
return None
|
||||||
|
with urllib.request.urlopen(url, timeout=5) as resp:
|
||||||
targets = json.loads(resp.read())
|
targets = json.loads(resp.read())
|
||||||
except (OSError, ValueError):
|
except (OSError, ValueError):
|
||||||
return None
|
return None
|
||||||
|
|||||||
@ -135,7 +135,7 @@ def _ensure_steam_running() -> None:
|
|||||||
# Check if any steam process is running (main client, not just helpers).
|
# Check if any steam process is running (main client, not just helpers).
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["pgrep", "-f", "steam.sh"], # noqa: S607
|
["/usr/bin/pgrep", "-f", "steam.sh"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
check=False,
|
check=False,
|
||||||
@ -937,7 +937,7 @@ def cmd_reset(config: Config, state: State) -> None:
|
|||||||
count = unhide_all_games(owned)
|
count = unhide_all_games(owned)
|
||||||
if count:
|
if count:
|
||||||
_echo(f"Unhidden {count} games.")
|
_echo(f"Unhidden {count} games.")
|
||||||
except Exception as exc: # noqa: BLE001
|
except (OSError, RuntimeError, ValueError) as exc:
|
||||||
_echo(f"Warning: could not unhide games: {exc}")
|
_echo(f"Warning: could not unhide games: {exc}")
|
||||||
|
|
||||||
state.current_app_id = None
|
state.current_app_id = None
|
||||||
@ -1024,7 +1024,7 @@ def _get_all_owned_app_ids(config: Config) -> list[int]:
|
|||||||
client = SteamAPIClient(config.steam_api_key, config.steam_id)
|
client = SteamAPIClient(config.steam_api_key, config.steam_id)
|
||||||
owned = client.get_owned_games()
|
owned = client.get_owned_games()
|
||||||
return [g["appid"] for g in owned]
|
return [g["appid"] for g in owned]
|
||||||
except Exception: # noqa: BLE001
|
except (OSError, RuntimeError, ValueError):
|
||||||
logger.warning("Could not fetch owned game list for hiding.")
|
logger.warning("Could not fetch owned game list for hiding.")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,8 @@ PROTONDB_CACHE_FILE = CONFIG_DIR / "protondb_cache.json"
|
|||||||
_PROTONDB_API = "https://www.protondb.com/api/v1/reports/summaries/{app_id}.json"
|
_PROTONDB_API = "https://www.protondb.com/api/v1/reports/summaries/{app_id}.json"
|
||||||
MAX_CONCURRENT = 30 # parallel requests - be polite to the CDN
|
MAX_CONCURRENT = 30 # parallel requests - be polite to the CDN
|
||||||
|
|
||||||
|
HTTP_NOT_FOUND = 404
|
||||||
|
|
||||||
# Tier ordering from best to worst.
|
# Tier ordering from best to worst.
|
||||||
TIER_ORDER: dict[str, int] = {
|
TIER_ORDER: dict[str, int] = {
|
||||||
"native": 0,
|
"native": 0,
|
||||||
@ -101,7 +103,7 @@ async def _fetch_one(
|
|||||||
async with sem:
|
async with sem:
|
||||||
try:
|
try:
|
||||||
async with session.get(url, timeout=aiohttp.ClientTimeout(total=15)) as r:
|
async with session.get(url, timeout=aiohttp.ClientTimeout(total=15)) as r:
|
||||||
if r.status == 404: # noqa: PLR2004
|
if r.status == HTTP_NOT_FOUND:
|
||||||
return ProtonDBRating(app_id=app_id)
|
return ProtonDBRating(app_id=app_id)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
data = await r.json(content_type=None)
|
data = await r.json(content_type=None)
|
||||||
@ -113,7 +115,7 @@ async def _fetch_one(
|
|||||||
confidence=data.get("confidence", ""),
|
confidence=data.get("confidence", ""),
|
||||||
total_reports=data.get("total", 0),
|
total_reports=data.get("total", 0),
|
||||||
)
|
)
|
||||||
except Exception: # noqa: BLE001
|
except (aiohttp.ClientError, asyncio.TimeoutError, OSError):
|
||||||
logger.warning("ProtonDB fetch failed for AppID=%d", app_id)
|
logger.warning("ProtonDB fetch failed for AppID=%d", app_id)
|
||||||
return ProtonDBRating(app_id=app_id)
|
return ProtonDBRating(app_id=app_id)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user