1
0

Improved robustness of FUSE
Some checks failed
Test Python / Test (push) Failing after 26s

This commit is contained in:
Jon Michael Aanes 2024-09-30 13:57:34 +02:00
parent 412a20b006
commit c09245bd1c
4 changed files with 70 additions and 39 deletions

View File

@ -15,16 +15,29 @@ your organization's payment plan.
Uses [`python-fuse`](https://github.com/libfuse/python-fuse) library.
Features:
- Local access to cards in todolist.
- Read card features:
- Title
- Description
- Tags
- Assignees
- Dependencies
- Change card features:
- Title
- Description
- [Obsidian](https://obsidian.md/) compatibility:
- Mountable within your vault.
- Link to cards by either card number or card title.
- Tags and dependencies are integrated.
Limitations:
- Only cards in todolist is fetched at the moment.
- Doesn't include title anywhere.
- Tasks cannot be updated or changed.
- Slow, due to inefficient use of caches.
A more complete implementation will probably require a Markdown parser, to
parse the saved input, and distribute it across the various Card fields (card
name, description, tasks, etc...)
- Tasks (checklists on cards) cannot be updated or changed.
- Images cannot be updated or changed.
- You cannot create new cards, nor any other files.
## Usage
@ -35,23 +48,27 @@ name, description, tasks, etc...)
[`python-fuse`](https://github.com/libfuse/python-fuse) implements a whole
bunch automatically.)
## Architecture
## Dependencies
- `FavroFuse`
- Markdown Parser/Renderer
- `FavroClient`
- `CardCache`
All requirements can be installed easily using:
## Work in Progress
```bash
pip install -r requirements.txt
```
Following features are work in progress:
Full list of requirements:
- [requests](https://pypi.org/project/requests/)
- [requests-cache](https://pypi.org/project/requests-cache/)
- [fuse-python](https://pypi.org/project/fuse-python/)
- [secret_loader](https://gitfub.space/Jmaa/secret_loader)
- [ ] Frontmatter: Update Tags
- [ ] Frontmatter: Updated assigned members
- [ ] Frontmatter: Arbitrary structured data? Read-only.
- [ ] Frontmatter: Dependencies. As vault links in Obsidian mode.
- [ ] Allow users to toggle Obsidian mode, instead of being default.
- [ ] Get the correct last-modified date.
- [ ] Improve cache behaviour. User and tags can have much longer cache times.
## License
# License
```
MIT License

View File

@ -36,28 +36,28 @@ CARD_FILENAME_REGEX = r'^\/PAR\-(\d+)\.md$'
OFFICIAL_URL='https://favro.com/organization/{org_id}?card=par-{seq_id}'
@dataclasses.dataclass(frozen=True)
class Thing:
class FileSystemItem:
@staticmethod
def from_path(path_str: str) -> 'Thing | None':
def from_path(path_str: str) -> 'FileSystemItem | None':
if path_str == '/':
return RootThing()
return RootFileSystemItem()
if m := re.match(CARD_FILENAME_REGEX, path_str):
return CardThing(SeqId(int(m.group(1))))
return CardFileSystemItem(SeqId(int(m.group(1))))
return None
@dataclasses.dataclass(frozen=True)
class RootThing(Thing):
class RootFileSystemItem(FileSystemItem):
pass
@dataclasses.dataclass(frozen=True)
class CardThing(Thing):
class CardFileSystemItem(FileSystemItem):
seq_id: SeqId
class FavroFuse(fuse.Fuse):
"""Favro Filesystem in Userspace."""
"""Favro FileSystem in Userspace."""
def __init__(
self,
@ -67,16 +67,17 @@ class FavroFuse(fuse.Fuse):
):
self.favro_client = favro_client
self.formatter = formatter
self.wiped_cards = set()
super().__init__(**kwargs)
def getattr(self, path: str) -> FavroStat | int:
thing = Thing.from_path(path)
thing = FileSystemItem.from_path(path)
st = FavroStat()
if isinstance(thing, RootThing):
if isinstance(thing, RootFileSystemItem):
st.st_mode = stat.S_IFDIR | 0o755
st.st_nlink = 2
elif isinstance(thing, CardThing):
elif isinstance(thing, CardFileSystemItem):
card = self.favro_client.get_card(thing.seq_id)
st.st_mode = stat.S_IFREG | 0o666
@ -97,19 +98,20 @@ 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 = Thing.from_path(path)
if not isinstance(thing, CardThing):
thing = FileSystemItem.from_path(path)
if not isinstance(thing, 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 = Thing.from_path(path)
if not isinstance(thing, CardThing):
thing = FileSystemItem.from_path(path)
if not isinstance(thing, CardFileSystemItem):
return -errno.ENOENT
card = self.favro_client.get_card(thing.seq_id)
contents_str = self._format_card_file(card)
contents = bytes(contents_str, 'utf8')
@ -124,8 +126,8 @@ class FavroFuse(fuse.Fuse):
def write(self, path: str, written_buffer: bytes, offset: int) -> int:
# Check that this is a card thing.
thing = Thing.from_path(path)
if not isinstance(thing, CardThing):
thing = FileSystemItem.from_path(path)
if not isinstance(thing, CardFileSystemItem):
return -errno.ENOENT
card = self.favro_client.get_card(thing.seq_id)
@ -140,13 +142,15 @@ 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)
# Return amount written
return len(written_buffer)
def truncate(self, path: str, new_size: int):
# Check that this is a card thing.
thing = Thing.from_path(path)
if not isinstance(thing, CardThing):
thing = FileSystemItem.from_path(path)
if not isinstance(thing, CardFileSystemItem):
return -errno.ENOENT
card = self.favro_client.get_card(thing.seq_id)
@ -163,10 +167,15 @@ 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)
# Return amount written
return 0
def _format_card_file(self, card: Card) -> str:
if card.seq_id in self.wiped_cards:
return ''
tags = [self.favro_client.get_tag(tag_id).name for tag_id in card.tags]
assignments = [
self.favro_client.get_user(assignment.user).name

View File

@ -39,8 +39,10 @@ class CardFileFormatter:
frontmatter_data = {}
if card.name and self.obsidian_mode:
frontmatter_data['aliases'] = [card.name]
frontmatter_data['tags'] = card.tags
frontmatter_data['url'] = card.url
if card.tags:
frontmatter_data['tags'] = card.tags
if card.url and self.obsidian_mode:
frontmatter_data['url'] = card.url
if card.assignments:
frontmatter_data['assignments'] = card.assignments
if self.obsidian_mode:
@ -94,6 +96,8 @@ class CardFileFormatter:
card_dependencies: list[str] = fm.metadata.get('dependencies', [])
card_dependencies = [parse_obsidian_link(text) for text in card_dependencies]
url: list[str] = fm.metadata.get('url')
description = self.renderer.render_children(document).strip()
return CardContents(
name,
@ -101,5 +105,5 @@ class CardFileFormatter:
tags=tags,
assignments=assignments,
card_dependencies=card_dependencies,
url='', # TODO
url=url,
)

View File

@ -40,6 +40,7 @@ EXAMPLE_TEXT_3 = """
---
aliases:
- 'Card: The Adventure of Card'
url: https://example.org
---
# Card: The Adventure of Card