[FUSE]: Generalizing path parsing
All checks were successful
Test Python / Test (push) Successful in 26s
All checks were successful
Test Python / Test (push) Successful in 26s
This commit is contained in:
parent
471704d9fe
commit
17ab429ed3
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue
Block a user