feat(state): combine markers and file tracking into single state file

This commit is contained in:
2026-02-19 14:12:43 +00:00
parent 2bc838fa6d
commit 8ad9917754
3 changed files with 68 additions and 82 deletions

View File

@@ -2,9 +2,8 @@
import os import os
import datetime import datetime
import subprocess 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 rewind.paths import load_config
from tqdm import tqdm from tqdm import tqdm
@@ -52,34 +51,11 @@ def mark(name: str) -> None:
if not name: if not name:
raise ValueError("Marker name cannot be empty") raise ValueError("Marker name cannot be empty")
if marker_exists(name): add_marker_to_state(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)
print(f"Added marker: {name}") print(f"Added marker: {name}")
def get_marker_timestamp(name: str) -> float: def get_marker_timestamp(name: str) -> float:
markers_file = os.path.join(os.path.dirname(__file__), "markers.json") markers = load_state().get("markers", [])
if not os.path.exists(markers_file):
raise RuntimeError("No markers found")
with open(markers_file, "r") as f:
markers = json.load(f)
for marker in markers: for marker in markers:
if marker["name"] == name: if marker["name"] == name:
@@ -88,40 +64,22 @@ def get_marker_timestamp(name: str) -> float:
raise ValueError("Marker name does not exist") raise ValueError("Marker name does not exist")
def print_markers() -> None: def print_markers() -> None:
markers_file = os.path.join(os.path.dirname(__file__), "markers.json") markers = load_state().get("markers", [])
if not os.path.exists(markers_file):
print("No markers found.")
return
with open(markers_file, "r") as f: if markers == []:
markers = json.load(f) print("No markers exist.")
for marker in markers: for marker in markers:
format_time = datetime.datetime.fromtimestamp(marker['timestamp']).strftime('%Y-%m-%d %H:%M:%S') format_time = datetime.datetime.fromtimestamp(marker['timestamp']).strftime('%Y-%m-%d %H:%M:%S')
print(f"{format_time} -> {marker['name']}") print(f"{format_time} -> {marker['name']}")
def remove_marker(name: str) -> None: def remove_marker(name: str) -> None:
markers_file = os.path.join(os.path.dirname(__file__), "markers.json") remove_marker_from_state(name)
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)
print(f"Removed marker: {name}") print(f"Removed marker: {name}")
def marker_exists(name: str) -> bool: def marker_exists(name: str) -> bool:
markers_file = os.path.join(os.path.dirname(__file__), "markers.json") markers = load_state().get("markers", [])
if not os.path.exists(markers_file): print(markers)
return False
with open(markers_file, "r") as f:
markers = json.load(f)
for marker in markers: for marker in markers:
if marker["name"] == name: if marker["name"] == name:

View File

@@ -5,16 +5,14 @@ import time
import obsws_python as obs import obsws_python as obs
import subprocess import subprocess
import logging import logging
import json
import shutil import shutil
import signal import signal
from threading import Lock
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
from rewind.paths import load_config from rewind.paths import load_config
from rewind.core import mark, marker_exists, remove_marker 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 running = True
@@ -59,6 +57,8 @@ def open_obs_connection(host: str, port: int, password: str) -> obs.ReqClient:
con = obs.ReqClient(host=host, port=port, password=password) con = obs.ReqClient(host=host, port=port, password=password)
except ConnectionRefusedError: except ConnectionRefusedError:
logger.info("OBS WebSocket not ready, retrying...") logger.info("OBS WebSocket not ready, retrying...")
except obs.events.OBSSDKError:
raise RuntimeError("Check OBS credentials")
if con: if con:
logger.info(f"Successfully connected to OBS at {host}:{port}") logger.info(f"Successfully connected to OBS at {host}:{port}")
@@ -93,23 +93,6 @@ def cleanup_physical_files(directory: str, max_age_seconds: int) -> None:
os.remove(file_path) os.remove(file_path)
logger.info(f"Removed old file: {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): class Handler(FileSystemEventHandler):
def on_created(self, event): def on_created(self, event):
if event.is_directory: if event.is_directory:
@@ -139,8 +122,7 @@ def main() -> None:
while running: while running:
cleanup_physical_files(recording_dir, config["record"]["max_record_time"]) cleanup_physical_files(recording_dir, config["record"]["max_record_time"])
cleanup_state_files() cleanup_state(config["record"]["max_record_time"])
cleanup_markers(config["record"]["max_record_time"])
time.sleep(INTERVAL) time.sleep(INTERVAL)
finally: finally:
if observer: if observer:

View File

@@ -7,17 +7,22 @@ from pathlib import Path
from rewind.paths import get_state_dir from rewind.paths import get_state_dir
STATE_NAME = "state.json" STATE_NAME = "state.json"
EMPTY_STATE = {
"files": [],
"markers": []
}
def get_state_file_path() -> Path: def get_state_file_path() -> Path:
return get_state_dir() / STATE_NAME return get_state_dir() / STATE_NAME
def load_state() -> dict: def load_state() -> dict:
if not get_state_file_path().exists():
return {"files": []}
with get_state_file_path().open() as f: with get_state_file_path().open() as f:
return json.load(f) return json.load(f)
def write_state(state: dict) -> None: 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") tmp = get_state_file_path().with_suffix(".tmp")
with tmp.open("w") as f: with tmp.open("w") as f:
json.dump(state, f, indent=2) json.dump(state, f, indent=2)
@@ -40,15 +45,56 @@ def add_file_to_state(file_path: str) -> None:
state["files"] = files state["files"] = files
write_state(state) 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() state = load_state()
files = state.get("files", []) 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) write_state(state)
def create_state_file_if_needed() -> None: def create_state_file_if_needed() -> None:
if not os.path.exists(get_state_file_path()): if not get_state_file_path().exists():
state = {"files": []} write_state(EMPTY_STATE)
write_state(state)