1
0

[FUSE]: Generalizing path parsing
All checks were successful
Test Python / Test (push) Successful in 26s

This commit is contained in:
Jon Michael Aanes 2024-10-02 16:59:27 +02:00
parent 471704d9fe
commit 17ab429ed3

View File

@ -11,52 +11,20 @@ from .favro_client import FavroClient
from .favro_data_model import Card, SeqId, CustomFieldInfo, CustomFieldItemId, CustomField from .favro_data_model import Card, SeqId, CustomFieldInfo, CustomFieldItemId, CustomField
from .favro_markdown import CardContents, CardFileFormatter from .favro_markdown import CardContents, CardFileFormatter
################################################################################
# Constants
logger = getLogger(__name__) logger = getLogger(__name__)
fuse.fuse_python_api = (0, 2) fuse.fuse_python_api = (0, 2)
OFFICIAL_URL = 'https://favro.com/organization/{org_id}?card=par-{seq_id}'
class FavroStat(fuse.Stat):
def __init__(self):
self.st_mode = 0
self.st_ino = 0
self.st_dev = 0
self.st_nlink = 0
self.st_uid = 0
self.st_gid = 0
self.st_size = 0
self.st_atime = 0
self.st_mtime = 0
self.st_ctime = 0
CARD_IDENTIFIER_FORMAT = 'PAR-{seq_id}' CARD_IDENTIFIER_FORMAT = 'PAR-{seq_id}'
CARD_FILENAME_FORMAT = CARD_IDENTIFIER_FORMAT + '.md' CARD_FILENAME_FORMAT = CARD_IDENTIFIER_FORMAT + '.md'
CARD_FILENAME_REGEX = r'^\/PAR\-(\d+)\.md$' CARD_FILENAME_REGEX = r'^PAR\-(\d+)\.md$'
OFFICIAL_URL = 'https://favro.com/organization/{org_id}?card=par-{seq_id}'
@dataclasses.dataclass(frozen=True)
class FileSystemItem:
@staticmethod
def from_path(path_str: str) -> 'FileSystemItem | None':
if path_str == '/':
return RootFileSystemItem()
if m := re.match(CARD_FILENAME_REGEX, path_str):
return CardFileSystemItem(SeqId(int(m.group(1))))
return None
@dataclasses.dataclass(frozen=True)
class RootFileSystemItem(FileSystemItem):
pass
@dataclasses.dataclass(frozen=True)
class CardFileSystemItem(FileSystemItem):
seq_id: SeqId
################################################################################
# Formatting
def to_custom_field_value(custom_field: CustomField, field_def: CustomFieldInfo) -> str: def to_custom_field_value(custom_field: CustomField, field_def: CustomFieldInfo) -> str:
value: CustomFieldItemId | list[CustomFieldItemId] = custom_field.value value: CustomFieldItemId | list[CustomFieldItemId] = custom_field.value
@ -107,6 +75,61 @@ def to_card_contents(card: Card, favro_client: FavroClient) -> str:
) )
################################################################################
# FUSE
class FavroStat(fuse.Stat):
def __init__(self):
self.st_mode = 0
self.st_ino = 0
self.st_dev = 0
self.st_nlink = 0
self.st_uid = 0
self.st_gid = 0
self.st_size = 0
self.st_atime = 0
self.st_mtime = 0
self.st_ctime = 0
@dataclasses.dataclass(frozen=True)
class FileSystemItem:
pass
@dataclasses.dataclass(frozen=True)
class RootFileSystemItem(FileSystemItem):
@staticmethod
def from_path_segment(segment: str) -> 'RootFileSystemItem':
return RootFileSystemItem()
@dataclasses.dataclass(frozen=True)
class OrganizationFileSystemItem(FileSystemItem):
name: str
@staticmethod
def from_path_segment(segment: str) -> 'OrganizationFileSystemItem':
return OrganizationFileSystemItem(segment)
@dataclasses.dataclass(frozen=True)
class CardFileSystemItem(FileSystemItem):
seq_id: SeqId
@staticmethod
def from_path_segment(segment: str) -> 'CardFileSystemItem | None':
if m := re.match(CARD_FILENAME_REGEX, segment):
return CardFileSystemItem(SeqId(int(m.group(1))))
return None
def path_to_file_system_item(path_str: str, path_components: list[type[FileSystemItem]]) -> FileSystemItem | None:
path = re.findall(r'[^/]+', path_str)
component = path_components[len(path)]
return component.from_path_segment(path[-1] if path else None)
class FavroFuse(fuse.Fuse): class FavroFuse(fuse.Fuse):
"""Favro FileSystem in Userspace.""" """Favro FileSystem in Userspace."""
@ -119,17 +142,19 @@ class FavroFuse(fuse.Fuse):
self.favro_client = favro_client self.favro_client = favro_client
self.formatter = formatter self.formatter = formatter
self.wiped_cards = set() self.wiped_cards = set()
#self.path_components = [RootFileSystemItem, OrganizationFileSystemItem, CardFileSystemItem]
self.path_components = [RootFileSystemItem, CardFileSystemItem]
super().__init__(**kwargs) super().__init__(**kwargs)
def getattr(self, path: str) -> FavroStat | int: def getattr(self, path: str) -> FavroStat | int:
thing = FileSystemItem.from_path(path) file_system_item = path_to_file_system_item(path, self.path_components)
st = FavroStat() st = FavroStat()
if isinstance(thing, RootFileSystemItem): if isinstance(file_system_item, RootFileSystemItem):
st.st_mode = stat.S_IFDIR | 0o755 st.st_mode = stat.S_IFDIR | 0o755
st.st_nlink = 2 st.st_nlink = 2
elif isinstance(thing, CardFileSystemItem): elif isinstance(file_system_item, CardFileSystemItem):
card = self.favro_client.get_card(thing.seq_id) card = self.favro_client.get_card(file_system_item.seq_id)
st.st_mode = stat.S_IFREG | 0o666 st.st_mode = stat.S_IFREG | 0o666
st.st_nlink = 1 st.st_nlink = 1
@ -149,18 +174,18 @@ class FavroFuse(fuse.Fuse):
yield fuse.Direntry(CARD_FILENAME_FORMAT.format(seq_id=card.seq_id.raw_id)) yield fuse.Direntry(CARD_FILENAME_FORMAT.format(seq_id=card.seq_id.raw_id))
def open(self, path: str, flags: int) -> int | None: def open(self, path: str, flags: int) -> int | None:
thing = FileSystemItem.from_path(path) file_system_item = path_to_file_system_item(path, self.path_components)
if not isinstance(thing, CardFileSystemItem): if not isinstance(file_system_item, CardFileSystemItem):
return -errno.ENOENT return -errno.ENOENT
return None return None
def read(self, path: str, size: int, offset: int) -> bytes | int: def read(self, path: str, size: int, offset: int) -> bytes | int:
# Check that this is a card thing. # Check that this is a card file_system_item.
thing = FileSystemItem.from_path(path) file_system_item = path_to_file_system_item(path, self.path_components)
if not isinstance(thing, CardFileSystemItem): if not isinstance(file_system_item, CardFileSystemItem):
return -errno.ENOENT return -errno.ENOENT
card = self.favro_client.get_card(thing.seq_id) card = self.favro_client.get_card(file_system_item.seq_id)
contents_str = self._format_card_file(card) contents_str = self._format_card_file(card)
contents = bytes(contents_str, 'utf8') contents = bytes(contents_str, 'utf8')
@ -175,12 +200,12 @@ class FavroFuse(fuse.Fuse):
return buf return buf
def write(self, path: str, written_buffer: bytes, offset: int) -> int: def write(self, path: str, written_buffer: bytes, offset: int) -> int:
# Check that this is a card thing. # Check that this is a card file_system_item.
thing = FileSystemItem.from_path(path) file_system_item = path_to_file_system_item(path, self.path_components)
if not isinstance(thing, CardFileSystemItem): if not isinstance(file_system_item, CardFileSystemItem):
return -errno.ENOENT return -errno.ENOENT
card = self.favro_client.get_card(thing.seq_id) card = self.favro_client.get_card(file_system_item.seq_id)
# Splice contents # Splice contents
contents_str = self._format_card_file(card) contents_str = self._format_card_file(card)
@ -192,18 +217,18 @@ class FavroFuse(fuse.Fuse):
card_updated = self.formatter.parse_card_contents(contents_str) card_updated = self.formatter.parse_card_contents(contents_str)
self.favro_client.update_card_contents(card.card_id, card_updated) self.favro_client.update_card_contents(card.card_id, card_updated)
self.wiped_cards.remove(thing.seq_id) self.wiped_cards.remove(file_system_item.seq_id)
# Return amount written # Return amount written
return len(written_buffer) return len(written_buffer)
def truncate(self, path: str, new_size: int): def truncate(self, path: str, new_size: int):
# Check that this is a card thing. # Check that this is a card file_system_item.
thing = FileSystemItem.from_path(path) file_system_item = path_to_file_system_item(path, self.path_components)
if not isinstance(thing, CardFileSystemItem): if not isinstance(file_system_item, CardFileSystemItem):
return -errno.ENOENT return -errno.ENOENT
card = self.favro_client.get_card(thing.seq_id) card = self.favro_client.get_card(file_system_item.seq_id)
# Splice contents # Splice contents
contents_str = self._format_card_file(card) contents_str = self._format_card_file(card)
@ -217,7 +242,7 @@ class FavroFuse(fuse.Fuse):
card_updated = self.formatter.parse_card_contents(contents_str) card_updated = self.formatter.parse_card_contents(contents_str)
self.favro_client.update_card_contents_locally(card.card_id, card_updated) self.favro_client.update_card_contents_locally(card.card_id, card_updated)
self.wiped_cards.add(thing.seq_id) self.wiped_cards.add(file_system_item.seq_id)
# Return amount written # Return amount written
return 0 return 0