Add .gitattributes: LF in the repository, CRLF for batch files
Normalize the files that had been committed with CRLF (line endings only, no content changes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e1d7b29f77
commit
51a7e77e56
14 files changed
+1461
-1447
No files matched your search
+69
-69
@@ -1,69 +1,69 @@
|
||||
"""Content-addressed blob store.
|
||||
|
||||
A blob id is ``<sha256><ext>`` (e.g. ``3fa1….webp``); the file lives at
|
||||
``blobs/3f/a1/<id>``. The extension is part of the id so nginx can serve
|
||||
blobs with the right Content-Type straight from disk.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
BLOB_ID_RE = re.compile(r"^[0-9a-f]{64}(\.[a-z0-9]{1,8})?$")
|
||||
_CHUNK = 1 << 20
|
||||
|
||||
|
||||
def blob_path(blob_id: str) -> Path:
|
||||
if not BLOB_ID_RE.match(blob_id):
|
||||
raise ValueError(f"bad blob id {blob_id!r}")
|
||||
return get_settings().blobs_dir / blob_id[:2] / blob_id[2:4] / blob_id
|
||||
|
||||
|
||||
def _finalize(tmp: Path, digest: str, ext: str) -> tuple[str, int]:
|
||||
blob_id = digest + ext.lower()
|
||||
dest = blob_path(blob_id)
|
||||
size = tmp.stat().st_size
|
||||
if dest.exists():
|
||||
tmp.unlink()
|
||||
else:
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp.chmod(0o644) # mkstemp creates 0600; nginx serves blobs as another user
|
||||
os.replace(tmp, dest)
|
||||
return blob_id, size
|
||||
|
||||
|
||||
def _tmp_file() -> Path:
|
||||
root = get_settings().blobs_dir / "tmp"
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
fd, name = tempfile.mkstemp(dir=root)
|
||||
os.close(fd)
|
||||
return Path(name)
|
||||
|
||||
|
||||
def put_file(src: Path, ext: str, move: bool = False) -> tuple[str, int]:
|
||||
"""Store a file, returning (blob id, size). ``move`` consumes ``src``."""
|
||||
h = hashlib.sha256()
|
||||
with src.open("rb") as f:
|
||||
while chunk := f.read(_CHUNK):
|
||||
h.update(chunk)
|
||||
tmp = _tmp_file()
|
||||
if move:
|
||||
shutil.move(src, tmp)
|
||||
else:
|
||||
shutil.copyfile(src, tmp)
|
||||
return _finalize(tmp, h.hexdigest(), ext)
|
||||
|
||||
|
||||
def put_bytes(data: bytes, ext: str) -> tuple[str, int]:
|
||||
tmp = _tmp_file()
|
||||
tmp.write_bytes(data)
|
||||
return _finalize(tmp, hashlib.sha256(data).hexdigest(), ext)
|
||||
|
||||
|
||||
def read_bytes(blob_id: str) -> bytes:
|
||||
return blob_path(blob_id).read_bytes()
|
||||
"""Content-addressed blob store.
|
||||
|
||||
A blob id is ``<sha256><ext>`` (e.g. ``3fa1….webp``); the file lives at
|
||||
``blobs/3f/a1/<id>``. The extension is part of the id so nginx can serve
|
||||
blobs with the right Content-Type straight from disk.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from app.config import get_settings
|
||||
|
||||
BLOB_ID_RE = re.compile(r"^[0-9a-f]{64}(\.[a-z0-9]{1,8})?$")
|
||||
_CHUNK = 1 << 20
|
||||
|
||||
|
||||
def blob_path(blob_id: str) -> Path:
|
||||
if not BLOB_ID_RE.match(blob_id):
|
||||
raise ValueError(f"bad blob id {blob_id!r}")
|
||||
return get_settings().blobs_dir / blob_id[:2] / blob_id[2:4] / blob_id
|
||||
|
||||
|
||||
def _finalize(tmp: Path, digest: str, ext: str) -> tuple[str, int]:
|
||||
blob_id = digest + ext.lower()
|
||||
dest = blob_path(blob_id)
|
||||
size = tmp.stat().st_size
|
||||
if dest.exists():
|
||||
tmp.unlink()
|
||||
else:
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp.chmod(0o644) # mkstemp creates 0600; nginx serves blobs as another user
|
||||
os.replace(tmp, dest)
|
||||
return blob_id, size
|
||||
|
||||
|
||||
def _tmp_file() -> Path:
|
||||
root = get_settings().blobs_dir / "tmp"
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
fd, name = tempfile.mkstemp(dir=root)
|
||||
os.close(fd)
|
||||
return Path(name)
|
||||
|
||||
|
||||
def put_file(src: Path, ext: str, move: bool = False) -> tuple[str, int]:
|
||||
"""Store a file, returning (blob id, size). ``move`` consumes ``src``."""
|
||||
h = hashlib.sha256()
|
||||
with src.open("rb") as f:
|
||||
while chunk := f.read(_CHUNK):
|
||||
h.update(chunk)
|
||||
tmp = _tmp_file()
|
||||
if move:
|
||||
shutil.move(src, tmp)
|
||||
else:
|
||||
shutil.copyfile(src, tmp)
|
||||
return _finalize(tmp, h.hexdigest(), ext)
|
||||
|
||||
|
||||
def put_bytes(data: bytes, ext: str) -> tuple[str, int]:
|
||||
tmp = _tmp_file()
|
||||
tmp.write_bytes(data)
|
||||
return _finalize(tmp, hashlib.sha256(data).hexdigest(), ext)
|
||||
|
||||
|
||||
def read_bytes(blob_id: str) -> bytes:
|
||||
return blob_path(blob_id).read_bytes()
|
||||
Reference in new issue
Block a user