1
0

Working on writing functionality

This commit is contained in:
Jon Michael Aanes 2024-09-26 21:51:53 +02:00
parent 58232b081a
commit cd3d1e3f33
Signed by: Jmaa
SSH Key Fingerprint: SHA256:Ab0GfHGCblESJx7JRE4fj4bFy/KRpeLhi41y4pF3sNA
4 changed files with 120 additions and 29 deletions

View File

@ -6,4 +6,6 @@ Synchronize your local notes and your Favro.
Uses the [Favro API](https://favro.com/developer/). Rate limiting depends upon Uses the [Favro API](https://favro.com/developer/). Rate limiting depends upon
your organization's payment plan. your organization's payment plan.
Uses [`python-fuse`](https://github.com/libfuse/python-fuse) library.
""" """

View File

@ -12,7 +12,9 @@ def main():
favro_username = secrets.load_or_fail('FAVRO_USERNAME') favro_username = secrets.load_or_fail('FAVRO_USERNAME')
favro_password = secrets.load_or_fail('FAVRO_PASSWORD') favro_password = secrets.load_or_fail('FAVRO_PASSWORD')
with tempfile.TemporaryDirectory(prefix='favro_sync-') as tmpdirname: #with tempfile.TemporaryDirectory(prefix='favro_sync_') as tmpdirname:
tmpdirname = './output' # TODO
if True:
session = requests_cache.CachedSession(tmpdirname + '/http-cache.sqlite', expire_after=360) session = requests_cache.CachedSession(tmpdirname + '/http-cache.sqlite', expire_after=360)
client = FavroClient(favro_org_id=OrganizationId(favro_org_id), client = FavroClient(favro_org_id=OrganizationId(favro_org_id),

View File

@ -46,11 +46,6 @@ class Card:
detailed_description: str | None detailed_description: str | None
# Derived
seq_id_with_prefix: str
''' TODO, fieds: ''' TODO, fieds:
'position': -399 'position': -399
'listPosition': -399 'listPosition': -399
@ -82,8 +77,6 @@ class Card:
tags = json['tags'], tags = json['tags'],
creator_user_id = UserId(json['createdByUserId']), creator_user_id = UserId(json['createdByUserId']),
creation_date = datetime.datetime.fromisoformat(json['createdAt']), creation_date = datetime.datetime.fromisoformat(json['createdAt']),
seq_id_with_prefix = PREFIX + str(json['sequentialId']),
) )
# Endpoints # Endpoints
@ -115,11 +108,11 @@ class FavroClient:
yield from self.get_cards(todo_list=True) yield from self.get_cards(todo_list=True)
def get_cards(self, *, seqid: SeqId | None = None, todo_list=True) -> Iterator[Card]: def get_cards(self, *, seq_id: SeqId | None = None, todo_list=True) -> Iterator[Card]:
# Determine params for get_cards # Determine params for get_cards
params = {'descriptionFormat': 'markdown'} params = {'descriptionFormat': 'markdown'}
if seqid: if seq_id:
params['cardSequentialId']= str(seqid.raw_id) params['cardSequentialId']= str(seq_id.raw_id)
if todo_list: if todo_list:
params['todoList'] = 'true' params['todoList'] = 'true'
@ -133,11 +126,11 @@ class FavroClient:
yield Card.from_json(entity_json) yield Card.from_json(entity_json)
del entity_json del entity_json
def get_card(self, seqid: SeqId) -> Card: def get_card(self, seq_id: SeqId) -> Card:
return next(self.get_cards(seqid=seqid)) return next(self.get_cards(seq_id=seq_id))
def get_card_id(self, seqid: SeqId) -> CardId: def get_card_id(self, seq_id: SeqId) -> CardId:
first_card = next(self.get_cards(seqid = seqid)) first_card = next(self.get_cards(seq_id = seq_id))
return first_card.card_id return first_card.card_id
def update_card_description(self, card_id: CardId, description: str) -> Card: def update_card_description(self, card_id: CardId, description: str) -> Card:

View File

@ -1,8 +1,10 @@
import os, stat, errno, fuse import os, stat, errno, fuse
from pathlib import Path from pathlib import Path
import re
import dataclasses
from collections.abc import Iterator from collections.abc import Iterator
from .favro_client import FavroClient from .favro_client import FavroClient, CardId, SeqId, Card
fuse.fuse_python_api = (0, 2) fuse.fuse_python_api = (0, 2)
@ -22,21 +24,59 @@ class MyStat(fuse.Stat):
self.st_mtime = 0 self.st_mtime = 0
self.st_ctime = 0 self.st_ctime = 0
CARD_FILENAME_FORMAT = 'PAR-{seq_id}.md'
CARD_FILENAME_REGEX = r'^\/PAR\-(\d+)\.md$'
@dataclasses.dataclass(frozen=True)
class Thing:
@staticmethod
def from_path(path_str: str) -> 'Thing | None':
if path_str == '/':
return RootThing()
if m := re.match(CARD_FILENAME_REGEX, path_str):
return CardThing(SeqId(int(m.group(1))))
return None
@dataclasses.dataclass(frozen=True)
class RootThing(Thing):
pass
@dataclasses.dataclass(frozen=True)
class CardThing(Thing):
seq_id: SeqId
def card_to_contents(card: Card) -> str:
ls = []
#ls.append('# ')
#ls.append(card.name)
#ls.append('\n\n')
ls.append(card.detailed_description or '')
return ''.join(ls)
class FavroFuse(fuse.Fuse): class FavroFuse(fuse.Fuse):
'''Favro Filesystem in Userspace.
'''
def __init__(self, favro_client: FavroClient, **kwargs): def __init__(self, favro_client: FavroClient, **kwargs):
self.favro_client = favro_client self.favro_client = favro_client
super().__init__(**kwargs) super().__init__(**kwargs)
def getattr(self, path: str) -> MyStat | int: def getattr(self, path: str) -> MyStat | int:
thing = Thing.from_path(path)
st = MyStat() st = MyStat()
if path == '/': if isinstance(thing, RootThing):
st.st_mode = stat.S_IFDIR | 0o755 st.st_mode = stat.S_IFDIR | 0o755
st.st_nlink = 2 st.st_nlink = 2
elif path == hello_path: elif isinstance(thing, CardThing):
st.st_mode = stat.S_IFREG | 0o444 card = self.favro_client.get_card(thing.seq_id)
st.st_mode = stat.S_IFREG | 0o666
st.st_nlink = 1 st.st_nlink = 1
st.st_size = len(hello_str) st.st_size = len(card_to_contents(card))
st.st_ctime = int(card.creation_date.timestamp())
st.st_mtime = st.st_ctime # TODO
else: else:
return -errno.ENOENT return -errno.ENOENT
return st return st
@ -46,27 +86,81 @@ class FavroFuse(fuse.Fuse):
yield fuse.Direntry('..') yield fuse.Direntry('..')
for card in self.favro_client.get_todo_list_cards(): for card in self.favro_client.get_todo_list_cards():
yield fuse.Direntry(card.seq_id_with_prefix) yield fuse.Direntry(CARD_FILENAME_FORMAT.format(seq_id=card.seq_id.raw_id))
return # TODO
def open(self, path: str, flags) -> int: def open(self, path: str, flags) -> int | None:
if path != hello_path: thing = Thing.from_path(path)
if not isinstance(thing, CardThing):
return -errno.ENOENT return -errno.ENOENT
accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR #accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
if (flags & accmode) != os.O_RDONLY: #if (flags & accmode) != os.O_RDONLY: return -errno.EACCES
return -errno.EACCES return None
def read(self, path: str, size: int, offset: int) -> bytes | int: def read(self, path: str, size: int, offset: int) -> bytes | int:
if path != hello_path: # Check that this is a card thing.
thing = Thing.from_path(path)
if not isinstance(thing, CardThing):
return -errno.ENOENT return -errno.ENOENT
slen = len(hello_str)
card = self.favro_client.get_card(thing.seq_id)
contents_str = card_to_contents(card)
contents = bytes(contents_str, 'utf8')
slen = len(contents)
if offset < slen: if offset < slen:
if offset + size > slen: if offset + size > slen:
size = slen - offset size = slen - offset
buf = hello_str[offset:offset+size] buf = contents[offset:offset+size]
else: else:
buf = b'' buf = b''
return buf return buf
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):
return -errno.ENOENT
card = self.favro_client.get_card(thing.seq_id)
# Splice contents
contents_str = card_to_contents(card)
contents = bytes(contents_str, 'utf8')
contents = splice(contents, written_buffer, offset)
contents_str = contents.decode('utf8')
# Write to favro
self.favro_client.update_card_description(card.card_id, contents_str)
# Return amount written
return len(written_buffer)
def truncate(self, path: str, new_size: int):
print('Trunca', path, new_size)
# Check that this is a card thing.
thing = Thing.from_path(path)
if not isinstance(thing, CardThing):
return -errno.ENOENT
card = self.favro_client.get_card(thing.seq_id)
# Splice contents
contents_str = card_to_contents(card)
contents = bytes(contents_str, 'utf8')
contents = contents[0:new_size]
contents_str = contents.decode('utf8')
# Write to favro
self.favro_client.update_card_description(card.card_id, contents_str)
# Return amount written
return 0
def splice(original_buffer: bytes, input_buffer: bytes, offset: int) -> bytes:
return original_buffer[0:offset-1] + input_buffer + original_buffer[offset+len(input_buffer)+1:len(original_buffer)]
HELP=""" HELP="""
Userspace hello example Userspace hello example