From 8ad9917754e0f580297fc14723334f737b400694 Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Thu, 19 Feb 2026 14:12:43 +0000 Subject: [PATCH] feat(state): combine markers and file tracking into single state file --- rewind/core.py | 60 +++++++--------------------------------------- rewind/daemon.py | 28 ++++------------------ rewind/state.py | 62 +++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 68 insertions(+), 82 deletions(-) diff --git a/rewind/core.py b/rewind/core.py index 67824bb..036cad8 100644 --- a/rewind/core.py +++ b/rewind/core.py @@ -2,9 +2,8 @@ import os import datetime import subprocess -import json -from rewind.state import load_state +from rewind.state import load_state, add_marker_to_state, remove_marker_from_state from rewind.paths import load_config from tqdm import tqdm @@ -52,34 +51,11 @@ def mark(name: str) -> None: if not name: raise ValueError("Marker name cannot be empty") - if marker_exists(name): - raise ValueError("Marker name already exists") - - # writes marker to json file (not state) - markers_file = os.path.join(os.path.dirname(__file__), "markers.json") - if os.path.exists(markers_file): - with open(markers_file, "r") as f: - markers = json.load(f) - else: - markers = [] - - markers.append({ - "name": name, - "timestamp": datetime.datetime.now().timestamp() - }) - - with open(markers_file, "w") as f: - json.dump(markers, f, indent=4) - + add_marker_to_state(name) print(f"Added marker: {name}") def get_marker_timestamp(name: str) -> float: - markers_file = os.path.join(os.path.dirname(__file__), "markers.json") - if not os.path.exists(markers_file): - raise RuntimeError("No markers found") - - with open(markers_file, "r") as f: - markers = json.load(f) + markers = load_state().get("markers", []) for marker in markers: if marker["name"] == name: @@ -88,40 +64,22 @@ def get_marker_timestamp(name: str) -> float: raise ValueError("Marker name does not exist") def print_markers() -> None: - markers_file = os.path.join(os.path.dirname(__file__), "markers.json") - if not os.path.exists(markers_file): - print("No markers found.") - return + markers = load_state().get("markers", []) - with open(markers_file, "r") as f: - markers = json.load(f) + if markers == []: + print("No markers exist.") for marker in markers: format_time = datetime.datetime.fromtimestamp(marker['timestamp']).strftime('%Y-%m-%d %H:%M:%S') print(f"{format_time} -> {marker['name']}") def remove_marker(name: str) -> None: - markers_file = os.path.join(os.path.dirname(__file__), "markers.json") - if not os.path.exists(markers_file): - raise RuntimeError("No markers found") - - with open(markers_file, "r") as f: - markers = json.load(f) - - markers = [m for m in markers if m["name"] != name] - - with open(markers_file, "w") as f: - json.dump(markers, f, indent=4) - + remove_marker_from_state(name) print(f"Removed marker: {name}") def marker_exists(name: str) -> bool: - markers_file = os.path.join(os.path.dirname(__file__), "markers.json") - if not os.path.exists(markers_file): - return False - - with open(markers_file, "r") as f: - markers = json.load(f) + markers = load_state().get("markers", []) + print(markers) for marker in markers: if marker["name"] == name: diff --git a/rewind/daemon.py b/rewind/daemon.py index 1352a0e..223213d 100755 --- a/rewind/daemon.py +++ b/rewind/daemon.py @@ -5,16 +5,14 @@ import time import obsws_python as obs import subprocess import logging -import json import shutil import signal -from threading import Lock from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from rewind.paths import load_config from rewind.core import mark, marker_exists, remove_marker -from rewind.state import add_file_to_state, create_state_file_if_needed, cleanup_state_files +from rewind.state import add_file_to_state, create_state_file_if_needed, cleanup_state running = True @@ -59,7 +57,9 @@ def open_obs_connection(host: str, port: int, password: str) -> obs.ReqClient: con = obs.ReqClient(host=host, port=port, password=password) except ConnectionRefusedError: logger.info("OBS WebSocket not ready, retrying...") - + except obs.events.OBSSDKError: + raise RuntimeError("Check OBS credentials") + if con: logger.info(f"Successfully connected to OBS at {host}:{port}") return con @@ -93,23 +93,6 @@ def cleanup_physical_files(directory: str, max_age_seconds: int) -> None: os.remove(file_path) logger.info(f"Removed old file: {file_path}") -def cleanup_markers(max_age_seconds: float) -> None: - markers_file = os.path.join(os.path.dirname(__file__), "markers.json") - if not os.path.exists(markers_file): - return - - with open(markers_file, "r") as f: - markers = json.load(f) - - current_time = datetime.datetime.now().timestamp() - new_markers = [m for m in markers if current_time - m['timestamp'] <= max_age_seconds] - - with open(markers_file, "w") as f: - json.dump(new_markers, f, indent=4) - - if new_markers != markers: - logger.info(f"Cleaning up {len(markers)-len(new_markers)} markers") - class Handler(FileSystemEventHandler): def on_created(self, event): if event.is_directory: @@ -139,8 +122,7 @@ def main() -> None: while running: cleanup_physical_files(recording_dir, config["record"]["max_record_time"]) - cleanup_state_files() - cleanup_markers(config["record"]["max_record_time"]) + cleanup_state(config["record"]["max_record_time"]) time.sleep(INTERVAL) finally: if observer: diff --git a/rewind/state.py b/rewind/state.py index 74f8ec4..ab12875 100644 --- a/rewind/state.py +++ b/rewind/state.py @@ -7,17 +7,22 @@ from pathlib import Path from rewind.paths import get_state_dir STATE_NAME = "state.json" +EMPTY_STATE = { + "files": [], + "markers": [] +} def get_state_file_path() -> Path: return get_state_dir() / STATE_NAME def load_state() -> dict: - if not get_state_file_path().exists(): - return {"files": []} with get_state_file_path().open() as f: return json.load(f) def write_state(state: dict) -> None: + if "files" not in state or "markers" not in state: + raise ValueError("Invalid state configuration. State must contain 'files' and 'markers' keys.") + tmp = get_state_file_path().with_suffix(".tmp") with tmp.open("w") as f: json.dump(state, f, indent=2) @@ -40,15 +45,56 @@ def add_file_to_state(file_path: str) -> None: state["files"] = files write_state(state) -def cleanup_state_files() -> None: +def add_marker_to_state(marker_name: str) -> None: + state = load_state() + markers = state.get("markers", []) + + existing_markers = {m["name"] for m in markers} + if marker_name in existing_markers: + raise ValueError("Marker already exists") + + markers.append({ + "name": marker_name, + "timestamp": datetime.datetime.now().timestamp() + }) + + state["markers"] = markers + write_state(state) + +def remove_marker_from_state(marker_name: str) -> None: + state = load_state() + markers = state.get("markers", []) + markers = [marker for marker in markers if marker["name"] != marker_name] + + state["markers"] = markers + write_state(state) + +def cleanup_state(max_age_seconds: float) -> None: state = load_state() files = state.get("files", []) + markers = state.get("markers", []) + now = datetime.datetime.now().timestamp() + + # Remove files that do not exist + state["files"] = [ + file for file in files + if os.path.exists(file["path"]) + ] + + # Remove files and markers beyond max age + state["files"] = [ + file for file in files + if now - file["timestamp"] <= max_age_seconds + ] + + + state["markers"] = [ + marker for marker in markers + if now - marker["timestamp"] <= max_age_seconds + ] - # Remove old files from state - state["files"] = [file for file in files if os.path.exists(file["path"])] write_state(state) def create_state_file_if_needed() -> None: - if not os.path.exists(get_state_file_path()): - state = {"files": []} - write_state(state) + if not get_state_file_path().exists(): + write_state(EMPTY_STATE)