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:
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
@@ -16,25 +17,4 @@ 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
|
||||
return tomllib.load(f)
|
||||
60
rewind/state.py
Normal file
60
rewind/state.py
Normal 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)
|
||||
Reference in New Issue
Block a user