flake history: make operations atomic
This commit is contained in:
@@ -5,19 +5,22 @@ from pathlib import Path
|
|||||||
from clan_cli.dirs import user_history_file
|
from clan_cli.dirs import user_history_file
|
||||||
|
|
||||||
from ..async_cmd import CmdOut, runforcli
|
from ..async_cmd import CmdOut, runforcli
|
||||||
|
from ..locked_open import locked_open
|
||||||
|
|
||||||
|
|
||||||
async def add_flake(path: Path) -> dict[str, CmdOut]:
|
async def add_flake(path: Path) -> dict[str, CmdOut]:
|
||||||
user_history_file().parent.mkdir(parents=True, exist_ok=True)
|
user_history_file().parent.mkdir(parents=True, exist_ok=True)
|
||||||
# append line to history file
|
# append line to history file
|
||||||
# TODO: Make this atomic
|
# TODO: Make this atomic
|
||||||
lines: set[str] = set()
|
lines: set = set()
|
||||||
if user_history_file().exists():
|
old_lines = set()
|
||||||
with open(user_history_file()) as f:
|
with locked_open(user_history_file(), "w+") as f:
|
||||||
lines = set(f.readlines())
|
old_lines = set(f.readlines())
|
||||||
lines.add(str(path))
|
lines = old_lines | {str(path)}
|
||||||
with open(user_history_file(), "w") as f:
|
if old_lines != lines:
|
||||||
|
f.seek(0)
|
||||||
f.writelines(lines)
|
f.writelines(lines)
|
||||||
|
f.truncate()
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ from pathlib import Path
|
|||||||
|
|
||||||
from clan_cli.dirs import user_history_file
|
from clan_cli.dirs import user_history_file
|
||||||
|
|
||||||
|
from ..locked_open import locked_open
|
||||||
|
|
||||||
|
|
||||||
def list_history() -> list[Path]:
|
def list_history() -> list[Path]:
|
||||||
if not user_history_file().exists():
|
if not user_history_file().exists():
|
||||||
return []
|
return []
|
||||||
# read path lines from history file
|
# read path lines from history file
|
||||||
with open(user_history_file()) as f:
|
with locked_open(user_history_file()) as f:
|
||||||
lines = f.readlines()
|
lines = f.readlines()
|
||||||
return [Path(line.strip()) for line in lines]
|
return [Path(line.strip()) for line in lines]
|
||||||
|
|
||||||
|
|||||||
15
pkgs/clan-cli/clan_cli/locked_open.py
Normal file
15
pkgs/clan-cli/clan_cli/locked_open.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import fcntl
|
||||||
|
from collections.abc import Generator
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def locked_open(filename: str | Path, mode: str = "r") -> Generator:
|
||||||
|
"""
|
||||||
|
This is a context manager that provides an advisory write lock on the file specified by `filename` when entering the context, and releases the lock when leaving the context. The lock is acquired using the `fcntl` module's `LOCK_EX` flag, which applies an exclusive write lock to the file.
|
||||||
|
"""
|
||||||
|
with open(filename, mode) as fd:
|
||||||
|
fcntl.flock(fd, fcntl.LOCK_EX)
|
||||||
|
yield fd
|
||||||
|
fcntl.flock(fd, fcntl.LOCK_UN)
|
||||||
Reference in New Issue
Block a user