refactor: extract state management into dedicated module and persist across restarts

Move state handling out of daemon and paths into a new state module.

- Introduce state.py for loading, writing, and maintaining state.json
- Persist recorded file metadata across daemon restarts
- Add cleanup of stale state entries when files are deleted
- Rename cleanup_old_files to cleanup_physical_files for clarity
- Ensure state file is created lazily if missing
This commit is contained in:
2026-01-07 20:15:54 +00:00
parent a135b3f140
commit 23d576c438
3 changed files with 69 additions and 42 deletions

View File

@@ -8,7 +8,8 @@ import subprocess
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from rewind.paths import load_state, write_state, load_config
from rewind.paths import load_config
from rewind.state import add_file_to_state, create_state_file_if_needed, cleanup_state_files
INTERVAL = 10
running = True
@@ -40,7 +41,7 @@ def stop_recording(con: obs.ReqClient) -> None:
con.stop_record()
print("Stopped recording")
def cleanup_old_files(directory: str, max_age_seconds: int) -> None:
def cleanup_physical_files(directory: str, max_age_seconds: int) -> None:
for filename in os.listdir(directory):
file_path = os.path.join(directory, filename)
if os.path.isfile(file_path):
@@ -49,22 +50,6 @@ def cleanup_old_files(directory: str, max_age_seconds: int) -> None:
os.remove(file_path)
print(f"Removed old file: {file_path}")
def create_state_file() -> None:
state = {"files": []}
write_state(state)
def add_file_to_state(file_path: str) -> None:
state = load_state()
files = state.get("files", [])
files.append({
"path": file_path,
"timestamp": datetime.datetime.now().timestamp(),
})
state["files"] = files
write_state(state)
def handle_shutdown(signum, frame):
global running
running = False
@@ -85,7 +70,8 @@ def main() -> None:
recording_dir = con.get_record_directory().record_directory
start_recording(con)
create_state_file()
create_state_file_if_needed()
try:
event_handler = Handler()
@@ -94,7 +80,8 @@ def main() -> None:
observer.start()
while running:
cleanup_old_files(recording_dir, config["record"]["max_record_time"])
cleanup_physical_files(recording_dir, config["record"]["max_record_time"])
cleanup_state_files()
time.sleep(INTERVAL)
finally:
stop_recording(con)

View File

@@ -8,6 +8,7 @@ from importlib import resources
APP_NAME = "rewind"
USER_CONFIG = Path.home() / ".rewind.toml"
STATE_NAME = "state.json"
def load_config() -> dict:
if USER_CONFIG.exists():
@@ -17,24 +18,3 @@ def load_config() -> dict:
# fallback to packaged default
with resources.files("rewind").joinpath("config.toml").open("rb") as f:
return tomllib.load(f)
def state_dir() -> Path:
base = os.path.expanduser("~/.local/share")
path = Path(base) / APP_NAME
path.mkdir(parents=True, exist_ok=True)
return path
def get_state_file_path() -> Path:
return state_dir() / "state.json"
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:
tmp = get_state_file_path().with_suffix(".tmp")
with tmp.open("w") as f:
json.dump(state, f, indent=2)
tmp.replace(get_state_file_path()) # atomic

60
rewind/state.py Normal file
View File

@@ -0,0 +1,60 @@
import datetime
import json
import os
from rewind.paths import load_config
from pathlib import Path
APP_NAME = "rewind"
STATE_NAME = "state.json"
def state_dir() -> Path:
base = os.path.expanduser("~/.local/share")
path = Path(base) / APP_NAME
path.mkdir(parents=True, exist_ok=True)
return path
def get_state_file_path() -> Path:
return 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:
tmp = get_state_file_path().with_suffix(".tmp")
with tmp.open("w") as f:
json.dump(state, f, indent=2)
tmp.replace(get_state_file_path()) # atomic
def add_file_to_state(file_path: str) -> None:
state = load_state()
files = state.get("files", [])
files.append({
"path": file_path,
"timestamp": datetime.datetime.now().timestamp(),
})
state["files"] = files
write_state(state)
def cleanup_state_files() -> None:
state = load_state()
files = state.get("files", [])
# Remove old files from state
for file in files:
if not os.path.exists(file["path"]):
files.remove(file)
print(f"Removed non-existent file from state: {file['path']}")
state["files"] = files
write_state(state)
def create_state_file_if_needed() -> None:
if not os.path.exists(get_state_file_path()):
state = {"files": []}
write_state(state)