Add more meta information to trove entries

This commit is contained in:
Andrew Mulbrook 2026-03-22 23:57:05 -05:00
parent 4ada169bbe
commit bd4d571c95
5 changed files with 80 additions and 7 deletions

View file

@ -71,7 +71,7 @@ class Sqlite3Trove:
con.commit()
obj = cls(con)
if initialize:
obj.write_blob(b"", NODE_ROOT_ID)
obj.write_blob(b"", NOTE_ROOT_ID)
return obj
def close(self):
@ -103,6 +103,15 @@ class Sqlite3Trove:
return None
return bytes(row["data"]) if row["data"] is not None else b""
def get_mtime(self, object_id: int) -> datetime | None:
"""Return the modified timestamp for an object, or None if not found."""
row = self._con.execute(
"SELECT modified FROM objects WHERE id = ?", (object_id,)
).fetchone()
if row is None:
return None
return datetime.fromisoformat(row["modified"])
def read_metadata(self, object_id: int, key: str) -> bytes | None:
"""Return raw metadata value for (uuid, key), or None if not found."""
row = self._con.execute(

View file

@ -1,6 +1,7 @@
import os
import sqlite3
import tempfile
import datetime as dt
from pathlib import Path
from typing import Optional, Dict, List, Self, Iterable
from .trove import Note, Trove, TreeNote, BlobNote, Blob, Tree, BadNoteType, TreeEntry, NoteNotFound
@ -24,6 +25,24 @@ class FSNote(Note):
raise ValueError("Note not yet saved to disk")
return self._inode
@property
def mtime(self):
"""Return modification time as datetime."""
stat = self._path.stat()
return dt.datetime.fromtimestamp(stat.st_mtime, tz=dt.timezone.utc)
@property
def readonly(self) -> bool:
"""Check if the note is readonly based on file permissions."""
if self._inode is None:
return False
return not os.access(self._path, os.W_OK)
@property
def mime(self) -> str:
"""Return MIME type, defaulting to generic binary stream."""
return "application/octet-stream"
@property
def _path(self) -> Path:
if self._fs_path is not None:
@ -60,6 +79,11 @@ class FSBlobNote(FSNote, BlobNote):
pass
class FSTreeNote(FSNote, TreeNote):
@property
def mime(self) -> str:
"""Return MIME type for directory/tree nodes."""
return "inode/directory"
def link(self, name: str, note: Note):
if not isinstance(note, FSBlobNote):
raise BadNoteType("Only blob notes can be linked")

View file

@ -194,20 +194,26 @@ class TroveFuseOps(pyfuse3.Operations):
attr.st_nlink = 1
attr.st_uid = os.getuid()
attr.st_gid = os.getgid()
mtime_ns = int(note.mtime.timestamp() * 1e9)
now_ns = int(time.time() * 1e9)
attr.st_atime_ns = now_ns
attr.st_mtime_ns = now_ns
attr.st_ctime_ns = now_ns
attr.st_mtime_ns = mtime_ns
attr.st_ctime_ns = mtime_ns
attr.generation = 0
attr.entry_timeout = 5.0
attr.attr_timeout = 5.0
# Determine permissions based on readonly property
if is_tree:
attr.st_mode = stat.S_IFDIR | 0o755
mode = 0o755 if not note.readonly else 0o555
attr.st_mode = stat.S_IFDIR | mode
attr.st_size = 0
attr.st_blksize = 512
attr.st_blocks = 0
else:
attr.st_mode = stat.S_IFREG | 0o644
mode = 0o644 if not note.readonly else 0o444
attr.st_mode = stat.S_IFREG | mode
attr.st_size = size
attr.st_blksize = 512
attr.st_blocks = (size + 511) // 512

View file

@ -1,6 +1,7 @@
from typing import Protocol, runtime_checkable, Optional, Dict, List, Self, NamedTuple, Iterable, TypedDict
from uuid import UUID
from pathlib import PurePosixPath
import datetime as dt
type ObjectId = int | str | UUID
@ -26,11 +27,27 @@ class Note(Protocol):
Protocol for a Note item.
Represents access to an individual note's content and metadata.
"""
@property
def object_id(self) -> ObjectId:
"""The unique identifier for this note."""
...
@property
def mime(self) -> str:
"""The MIME type of the note's content."""
...
@property
def readonly(self) -> bool:
"""Whether the note is read-only."""
...
@property
def mtime(self) -> dt.datetime:
"""The last modification time of the note."""
...
def get_raw_metadata(self, key: str) -> Optional[bytes]:
"""Retrieve metadata value for the given key."""
...
@ -84,10 +101,12 @@ class Tree(Protocol):
"""Return all entries as {name: object_id}."""
...
class BlobNote(Note, Blob):
@runtime_checkable
class BlobNote(Note, Blob, Protocol):
"""Blob Note"""
class TreeNote(Note, Tree):
@runtime_checkable
class TreeNote(Note, Tree, Protocol):
"""Tree Note"""

View file

@ -8,6 +8,7 @@ tree serialization respectively.
from typing import Optional, Self
from pathlib import Path
import datetime as dt
from .db import Sqlite3Trove, NOTE_ROOT_ID
from .tree import Tree as TreeData
@ -30,6 +31,20 @@ class NoteImpl(Note):
def object_id(self) -> int:
return self._object_id
@property
def readonly(self) -> bool:
return False
@property
def mtime(self) -> dt.datetime:
"""Return modification time as Unix timestamp, or None if not set."""
return self._db.get_mtime(self._object_id)
@property
def mime(self) -> str:
"""Return MIME type, defaulting to generic binary stream."""
return "application/octet-stream"
def get_raw_metadata(self, key: str) -> Optional[bytes]:
return self._db.read_metadata(self._object_id, key)