Old SandTable project
This commit is contained in:
+127
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Shared play/pause/stop/skip control for the sand table player.
|
||||
|
||||
Replaces the old OS-signal-based approach (SIGUSR1/SIGUSR2/SIGTERM caught
|
||||
by a handler with module-global flags, process discovery via psutil
|
||||
scanning cmdlines for "PlayQueue.py"). That worked but had some real
|
||||
downsides:
|
||||
|
||||
- views.py's "playing" flag was a plain Python global, so with more than
|
||||
one web worker process it could disagree with reality.
|
||||
- "is a player running" was answered by grepping every process's cmdline
|
||||
for a string, which is a little fragile.
|
||||
- Signals give no visibility into *what* state the player is actually in
|
||||
(playing/paused/idle) or which track - only views.py's own guess.
|
||||
|
||||
This module stores that state in a single row of a small SQLite table
|
||||
instead - the same DB the rest of the project already uses. The running
|
||||
player (playTrack.py) polls `get_command()` between points; the web app
|
||||
(views.py) writes to it with `set_command()` and reads real status with
|
||||
`get_status()`. No signals, no process-name matching for control - psutil
|
||||
is only still used as a safety net to force-kill a stuck process by its
|
||||
recorded PID.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import psutil
|
||||
|
||||
DB_PATH = "/home/pi/SandTable/db.sqlite3"
|
||||
TABLE = "sandtable_control"
|
||||
|
||||
# status: what the player is actually doing right now
|
||||
STATUS_IDLE = "idle"
|
||||
STATUS_PLAYING = "playing"
|
||||
STATUS_PAUSED = "paused"
|
||||
|
||||
# command: what the player has been asked to do - it's expected to notice
|
||||
# and react next time it checks
|
||||
COMMAND_PLAY = "play"
|
||||
COMMAND_PAUSE = "pause"
|
||||
COMMAND_STOP = "stop"
|
||||
COMMAND_NEXT = "next"
|
||||
|
||||
|
||||
def _connect():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.execute(
|
||||
f"""
|
||||
CREATE TABLE IF NOT EXISTS {TABLE} (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 0),
|
||||
command TEXT NOT NULL DEFAULT '{COMMAND_PLAY}',
|
||||
status TEXT NOT NULL DEFAULT '{STATUS_IDLE}',
|
||||
current_track TEXT,
|
||||
pid INTEGER
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
f"INSERT OR IGNORE INTO {TABLE} (id, command, status) VALUES (0, ?, ?)",
|
||||
(COMMAND_PLAY, STATUS_IDLE),
|
||||
)
|
||||
return conn
|
||||
|
||||
|
||||
def init_control_table():
|
||||
conn = _connect()
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def set_command(command):
|
||||
conn = _connect()
|
||||
conn.execute(f"UPDATE {TABLE} SET command = ? WHERE id = 0", (command,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_command():
|
||||
conn = _connect()
|
||||
row = conn.execute(f"SELECT command FROM {TABLE} WHERE id = 0").fetchone()
|
||||
conn.close()
|
||||
return row[0] if row else COMMAND_PLAY
|
||||
|
||||
|
||||
def set_status(status, current_track=None, pid=None):
|
||||
"""current_track/pid are only updated when explicitly passed - pass
|
||||
current_track='' or pid=0 if you actually want to clear them."""
|
||||
conn = _connect()
|
||||
if current_track is not None:
|
||||
conn.execute(f"UPDATE {TABLE} SET current_track = ? WHERE id = 0", (current_track,))
|
||||
if pid is not None:
|
||||
conn.execute(f"UPDATE {TABLE} SET pid = ? WHERE id = 0", (pid,))
|
||||
conn.execute(f"UPDATE {TABLE} SET status = ? WHERE id = 0", (status,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_status():
|
||||
conn = _connect()
|
||||
row = conn.execute(f"SELECT status, current_track, pid FROM {TABLE} WHERE id = 0").fetchone()
|
||||
conn.close()
|
||||
if row is None:
|
||||
return {"status": STATUS_IDLE, "current_track": None, "pid": None}
|
||||
return {"status": row[0], "current_track": row[1], "pid": row[2]}
|
||||
|
||||
|
||||
def stop_player(hard=True):
|
||||
"""Ask the running player to stop, and (by default) also force-kill its
|
||||
process by the PID it registered - useful right before a manual
|
||||
home/stop/shutdown, where you don't want to wait for the cooperative
|
||||
stop to be noticed. Returns True if a live player was found."""
|
||||
|
||||
status = get_status()
|
||||
pid = status.get("pid")
|
||||
found = False
|
||||
|
||||
if pid and psutil.pid_exists(pid):
|
||||
found = True
|
||||
if hard:
|
||||
try:
|
||||
psutil.Process(pid).terminate()
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
|
||||
set_command(COMMAND_STOP)
|
||||
set_status(STATUS_IDLE, pid=0)
|
||||
return found
|
||||
Reference in New Issue
Block a user