Start API refactor to remove separate Tree

We want trees to just be notes. We want to reference notes
by Path instead of object ids. Stop thinking about the
fs implementation and the fuse service with the same terms -
they will be different backends.
This commit is contained in:
Andrew Mulbrook 2026-03-26 21:00:35 -05:00
parent abbef64bbc
commit 41480a39c9
5 changed files with 245 additions and 234 deletions

View file

@ -5,9 +5,10 @@ Implements BlobNote, TreeNote, and Trove protocols defined in trove.py.
Depends on db.py (Sqlite3Trove) for storage.
"""
from typing import Optional
from typing import Optional, Iterator
from pathlib import Path
import datetime as dt
import uuid
from .db import Sqlite3Trove, NOTE_ROOT_ID
@ -20,9 +21,19 @@ class NoteImpl(Note):
"""Concrete note implementation."""
def __init__(self, parent: 'TroveImpl', object_id: ObjectId):
if not isinstance(object_id, uuid.UUID):
object_id = uuid.UUID(str(object_id))
assert isinstance(object_id, uuid.UUID)
self._parent = parent
self._db = parent.db
self._object_id = object_id
self._object_id: uuid.UUID = object_id
@staticmethod
def get_impl_id(note: Note) -> uuid.UUID:
if not isinstance(note.object_id, uuid.UUID):
raise TypeError("Note not compatible with NoteImpl")
return note.object_id
# Note protocol
@property
@ -36,7 +47,8 @@ class NoteImpl(Note):
@property
def mtime(self) -> dt.datetime:
"""Return modification time as UTC datetime."""
return self._db.get_mtime(self._object_id)
mtime = self._db.get_mtime(self._object_id)
return mtime if mtime is not None else dt.datetime.now(tz=dt.timezone.utc)
@property
def mime(self) -> str:
@ -57,6 +69,39 @@ class NoteImpl(Note):
def write_content(self, data: bytes) -> None:
self._db.write_content(self._object_id, data)
def children(self) -> Iterator[TreeEntry]:
"""Get all children of this note."""
for name, object_id in self._db.list_tree(self._object_id).items():
yield TreeEntry(name, object_id)
def new_child(self, name: str, mime: str, content: bytes | None, executable: bool, hidden: bool) -> Note:
"""Create a new child note."""
content = content if content is not None else b""
object_id = self._db.write_blob(data=content, object_id=None, dtype=mime, executable=executable, hidden=hidden)
# TODO fix this
if mime == 'inode/directory':
return TreeNoteImpl(self._parent, object_id)
return NoteImpl(self._parent, object_id)
def child(self, name: str) -> Note:
"""Retrieve a child note by name."""
entries = self._db.list_tree(self._object_id)
if name not in entries:
raise tr.ErrorNotFound(name)
child_id = entries[name]
value = self._parent.get_raw_note(child_id)
if value is None:
raise tr.ErrorNotFound("dangling child link") # FIXME: better errors
return value
def rm_child(self, name: str, recurse: bool) -> None:
"""Remove a child note."""
note = self.child(name)
if note.has_children() and not recurse:
raise tr.ErrorNotEmpty(name)
self._db.unlink(self._object_id, name)
class TreeNoteImpl(NoteImpl, TreeNote):
"""Concrete TreeNote: a tree object backed by the tree_entries table."""
@ -64,7 +109,7 @@ class TreeNoteImpl(NoteImpl, TreeNote):
# Tree protocol
def link(self, name: str, note: Note) -> None:
"""Link name to an existing note."""
self._db.link(self._object_id, name, note.object_id)
self._db.link(self._object_id, name, NoteImpl.get_impl_id(note))
def unlink(self, name: str) -> None:
"""Remove an entry by name."""
@ -81,25 +126,13 @@ class TreeNoteImpl(NoteImpl, TreeNote):
"""Remove a directory from the tree."""
self.unlink(name)
def child(self, name: str) -> Note:
"""Retrieve a child note by name."""
entries = self._db.list_tree(self._object_id)
if name not in entries:
raise KeyError(f"Entry '{name}' not found")
child_id = entries[name]
value = self._parent.get_raw_note(child_id)
if value is None:
raise KeyError(f"Entry '{name}' has no value")
return value
def entries(self):
"""Return all entries as an iterable of TreeEntry."""
for name, object_id in self._db.list_tree(self._object_id).items():
yield TreeEntry(name, object_id)
def list(self) -> dict[str, ObjectId]:
"""Return all entries as {name: object_id}."""
return self._db.list_tree(self._object_id)
# ---------------------------------------------------------------------------
@ -137,6 +170,8 @@ class TroveImpl:
# Trove protocol
def get_raw_note(self, note_id: ObjectId) -> Note:
"""Return a BlobNote or TreeNote for the given id, or None if not found."""
if not isinstance(note_id, uuid.UUID):
note_id = uuid.UUID(str(note_id))
info = self._db.get_info(note_id)
if info is None:
raise NoteNotFound(note_id)