fix: publish feeds atomically
This commit is contained in:
parent
cbb427b89d
commit
e64a32d76b
7 changed files with 253 additions and 4 deletions
|
|
@ -0,0 +1,47 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from contextlib import suppress
|
||||
from pathlib import Path
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from repub.config import feed_output_path, staged_feed_output_path
|
||||
|
||||
|
||||
def publish_staged_feed(*, out_dir: Path, feed_slug: str) -> Path:
|
||||
staged_path = staged_feed_output_path(out_dir=out_dir, feed_slug=feed_slug)
|
||||
public_path = feed_output_path(out_dir=out_dir, feed_slug=feed_slug)
|
||||
|
||||
public_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
_validate_staged_feed(staged_path)
|
||||
_fsync_file(staged_path)
|
||||
os.replace(staged_path, public_path)
|
||||
_fsync_directory(public_path.parent)
|
||||
return public_path
|
||||
|
||||
|
||||
def _fsync_file(path: Path) -> None:
|
||||
with path.open("rb") as handle:
|
||||
os.fsync(handle.fileno())
|
||||
|
||||
|
||||
def _validate_staged_feed(path: Path) -> None:
|
||||
try:
|
||||
root = ElementTree.parse(path).getroot()
|
||||
except ElementTree.ParseError as error:
|
||||
raise ValueError(f"Staged feed is not well-formed XML: {path}") from error
|
||||
|
||||
if root.tag != "rss":
|
||||
raise ValueError(f"Staged feed is not an RSS document: {path}")
|
||||
if root.find("channel") is None:
|
||||
raise ValueError(f"Staged feed is missing an RSS channel: {path}")
|
||||
|
||||
|
||||
def _fsync_directory(path: Path) -> None:
|
||||
flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0)
|
||||
with suppress(OSError):
|
||||
fd = os.open(path, flags)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
Loading…
Add table
Add a link
Reference in a new issue