diff --git a/favro_sync/favro_fuse.py b/favro_sync/favro_fuse.py index 96f0931..d92124f 100644 --- a/favro_sync/favro_fuse.py +++ b/favro_sync/favro_fuse.py @@ -11,52 +11,20 @@ from .favro_client import FavroClient from .favro_data_model import Card, SeqId, CustomFieldInfo, CustomFieldItemId, CustomField from .favro_markdown import CardContents, CardFileFormatter +################################################################################ +# Constants + logger = getLogger(__name__) fuse.fuse_python_api = (0, 2) - -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 - - +OFFICIAL_URL = 'https://favro.com/organization/{org_id}?card=par-{seq_id}' CARD_IDENTIFIER_FORMAT = 'PAR-{seq_id}' CARD_FILENAME_FORMAT = CARD_IDENTIFIER_FORMAT + '.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 +CARD_FILENAME_REGEX = r'^PAR\-(\d+)\.md$' +################################################################################ +# Formatting def to_custom_field_value(custom_field: CustomField, field_def: CustomFieldInfo) -> str: 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): """Favro FileSystem in Userspace.""" @@ -119,17 +142,19 @@ class FavroFuse(fuse.Fuse): self.favro_client = favro_client self.formatter = formatter self.wiped_cards = set() + #self.path_components = [RootFileSystemItem, OrganizationFileSystemItem, CardFileSystemItem] + self.path_components = [RootFileSystemItem, CardFileSystemItem] super().__init__(**kwargs) 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() - if isinstance(thing, RootFileSystemItem): + if isinstance(file_system_item, RootFileSystemItem): st.st_mode = stat.S_IFDIR | 0o755 st.st_nlink = 2 - elif isinstance(thing, CardFileSystemItem): - card = self.favro_client.get_card(thing.seq_id) + elif isinstance(file_system_item, CardFileSystemItem): + card = self.favro_client.get_card(file_system_item.seq_id) st.st_mode = stat.S_IFREG | 0o666 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)) def open(self, path: str, flags: int) -> int | None: - thing = FileSystemItem.from_path(path) - if not isinstance(thing, CardFileSystemItem): + file_system_item = path_to_file_system_item(path, self.path_components) + if not isinstance(file_system_item, CardFileSystemItem): return -errno.ENOENT return None def read(self, path: str, size: int, offset: int) -> bytes | int: - # Check that this is a card thing. - thing = FileSystemItem.from_path(path) - if not isinstance(thing, CardFileSystemItem): + # Check that this is a card file_system_item. + file_system_item = path_to_file_system_item(path, self.path_components) + if not isinstance(file_system_item, CardFileSystemItem): 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 = bytes(contents_str, 'utf8') @@ -175,12 +200,12 @@ class FavroFuse(fuse.Fuse): return buf def write(self, path: str, written_buffer: bytes, offset: int) -> int: - # Check that this is a card thing. - thing = FileSystemItem.from_path(path) - if not isinstance(thing, CardFileSystemItem): + # Check that this is a card file_system_item. + file_system_item = path_to_file_system_item(path, self.path_components) + if not isinstance(file_system_item, CardFileSystemItem): 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 contents_str = self._format_card_file(card) @@ -192,18 +217,18 @@ class FavroFuse(fuse.Fuse): card_updated = self.formatter.parse_card_contents(contents_str) 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 len(written_buffer) def truncate(self, path: str, new_size: int): - # Check that this is a card thing. - thing = FileSystemItem.from_path(path) - if not isinstance(thing, CardFileSystemItem): + # Check that this is a card file_system_item. + file_system_item = path_to_file_system_item(path, self.path_components) + if not isinstance(file_system_item, CardFileSystemItem): 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 contents_str = self._format_card_file(card) @@ -217,7 +242,7 @@ class FavroFuse(fuse.Fuse): card_updated = self.formatter.parse_card_contents(contents_str) 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 0