1
0

Added real-only support for tags and assignments

This commit is contained in:
Jon Michael Aanes 2024-09-28 12:53:34 +02:00
parent d4cfe4da22
commit 9fb19e39a5
Signed by: Jmaa
SSH Key Fingerprint: SHA256:Ab0GfHGCblESJx7JRE4fj4bFy/KRpeLhi41y4pF3sNA
5 changed files with 68 additions and 15 deletions

View File

@ -6,7 +6,7 @@ import requests_cache
from .favro_client import FavroClient, OrganizationId from .favro_client import FavroClient, OrganizationId
from .favro_fuse import start_favro_fuse from .favro_fuse import start_favro_fuse
import secrets from . import secrets
def main(): def main():

View File

@ -4,7 +4,8 @@ from logging import getLogger
import requests import requests
import dataclasses import dataclasses
from .favro_data_model import Card, CardId, OrganizationId, SeqId from .favro_data_model import (Card, CardId, OrganizationId, SeqId, UserInfo,
UserId, UserInfo, TagId, TagInfo)
from .favro_markdown import CardContents from .favro_markdown import CardContents
logger = getLogger(__name__) logger = getLogger(__name__)
@ -13,6 +14,8 @@ logger = getLogger(__name__)
URL_API_ROOT = 'https://favro.com/api/v1' URL_API_ROOT = 'https://favro.com/api/v1'
URL_GET_ALL_CARDS = URL_API_ROOT + '/cards' URL_GET_ALL_CARDS = URL_API_ROOT + '/cards'
URL_UPDATE_CARD = URL_API_ROOT + '/cards/{card_id}' URL_UPDATE_CARD = URL_API_ROOT + '/cards/{card_id}'
URL_GET_USER = URL_API_ROOT + '/users/{user_id}'
URL_GET_TAG = URL_API_ROOT + '/tags/{tag_id}'
class CardCache: class CardCache:
@ -27,16 +30,19 @@ class CardCache:
for card in reversed(self.cards): for card in reversed(self.cards):
if card.card_id == card_id: if card.card_id == card_id:
return card return card
return None
def get_card_by_seq_id(self, seq_id: SeqId) -> Card | None: def get_card_by_seq_id(self, seq_id: SeqId) -> Card | None:
for card in reversed(self.cards): for card in reversed(self.cards):
if card.seq_id == seq_id: if card.seq_id == seq_id:
return card return card
return None
def remove(self, card_id: CardId) -> Card | None: def remove(self, card_id: CardId) -> Card | None:
while card := self.get_card_by_card_id(card_id): while card := self.get_card_by_card_id(card_id):
self.cards.remove(card) self.cards.remove(card)
return card return card
return None
class FavroClient: class FavroClient:
def __init__( def __init__(
@ -85,7 +91,6 @@ class FavroClient:
# TODO: Add support for pageination # TODO: Add support for pageination
for entity_json in json['entities']: for entity_json in json['entities']:
print(entity_json)
card = Card.from_json(entity_json) card = Card.from_json(entity_json)
self.cache.add_card(card) self.cache.add_card(card)
yield card yield card
@ -108,6 +113,14 @@ class FavroClient:
return card return card
return next(self.get_cards(seq_id=seq_id)) return next(self.get_cards(seq_id=seq_id))
def get_user(self, user_id: UserId) -> UserInfo:
response = self.session.get(URL_GET_USER.format(user_id=user_id.raw_id))
return UserInfo.from_json(response.json())
def get_tag(self, tag_id: TagId) -> TagInfo:
response = self.session.get(URL_GET_TAG.format(tag_id=tag_id.raw_id))
return TagInfo.from_json(response.json())
def _invalidate_cache(self, card_id: CardId) -> None: def _invalidate_cache(self, card_id: CardId) -> None:
card = self.cache.remove(card_id) card = self.cache.remove(card_id)
if card: if card:
@ -125,12 +138,12 @@ class FavroClient:
"""Returns updated Card.""" """Returns updated Card."""
if self.read_only == 'silent': if self.read_only == 'silent':
logger.warning( logger.warning(
'FavroClient is silent read only: Discarding card description update of length %d', 'FavroClient is silent read only: Discarding card description update',
len(description),
) )
return None # TODO return None # TODO
elif self.read_only is True: if self.read_only is True:
raise Exception('FavroClient is read only') msg = 'FavroClient is read only'
raise Exception(msg)
json_body = { json_body = {
'name': card_contents.name, 'name': card_contents.name,

View File

@ -40,10 +40,42 @@ class CardAssignment:
json['completed'], json['completed'],
) )
@dataclasses.dataclass(frozen=True)
class UserInfo:
user_id: UserId
name: str
email: str
organization_role: str
@staticmethod
def from_json(json: dict[str, Any]) -> 'UserInfo':
return UserInfo(
UserId(json['userId']),
json['name'],
json['email'],
json['organizationRole'],
)
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class TagId: class TagId:
raw_id: str raw_id: str
@dataclasses.dataclass(frozen=True)
class TagInfo:
tag_id: TagId
organization_id: OrganizationId
name: str
color: str | None
@staticmethod
def from_json(json: dict[str, Any]) -> 'TagInfo':
return TagInfo(
TagId(json['tagId']),
OrganizationId(json['organizationId']),
json['name'],
json.get('color'),
)
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class CardDependency: class CardDependency:
card_id: CardId card_id: CardId

View File

@ -73,7 +73,7 @@ class FavroFuse(fuse.Fuse):
st.st_mode = stat.S_IFREG | 0o666 st.st_mode = stat.S_IFREG | 0o666
st.st_nlink = 1 st.st_nlink = 1
st.st_size = len(self.formatter.format_card(card)) st.st_size = len(self._format_card_file(card))
st.st_ctime = int(card.creation_date.timestamp()) st.st_ctime = int(card.creation_date.timestamp())
st.st_mtime = st.st_ctime # TODO st.st_mtime = st.st_ctime # TODO
else: else:
@ -88,7 +88,7 @@ class FavroFuse(fuse.Fuse):
for card in self.favro_client.get_todo_list_cards(): for card in self.favro_client.get_todo_list_cards():
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 | None: def open(self, path: str, flags: int) -> int | None:
thing = Thing.from_path(path) thing = Thing.from_path(path)
if not isinstance(thing, CardThing): if not isinstance(thing, CardThing):
return -errno.ENOENT return -errno.ENOENT
@ -102,7 +102,7 @@ class FavroFuse(fuse.Fuse):
card = self.favro_client.get_card(thing.seq_id) card = self.favro_client.get_card(thing.seq_id)
contents_str = self.formatter.format_card(card) contents_str = self._format_card_file(card)
contents = bytes(contents_str, 'utf8') contents = bytes(contents_str, 'utf8')
slen = len(contents) slen = len(contents)
@ -123,7 +123,7 @@ class FavroFuse(fuse.Fuse):
card = self.favro_client.get_card(thing.seq_id) card = self.favro_client.get_card(thing.seq_id)
# Splice contents # Splice contents
contents_str = self.formatter.format_card(card) contents_str = self._format_card_file(card)
contents = bytes(contents_str, 'utf8') contents = bytes(contents_str, 'utf8')
contents = splice(contents, written_buffer, offset) contents = splice(contents, written_buffer, offset)
contents_str = contents.decode('utf8') contents_str = contents.decode('utf8')
@ -144,7 +144,7 @@ class FavroFuse(fuse.Fuse):
card = self.favro_client.get_card(thing.seq_id) card = self.favro_client.get_card(thing.seq_id)
# Splice contents # Splice contents
contents_str = self.formatter.format_card(card) contents_str = self._format_card_file(card)
contents = bytes(contents_str, 'utf8') contents = bytes(contents_str, 'utf8')
old_size = len(contents) old_size = len(contents)
contents = contents[0:new_size] + b' ' * (old_size - new_size) contents = contents[0:new_size] + b' ' * (old_size - new_size)
@ -158,6 +158,16 @@ class FavroFuse(fuse.Fuse):
# Return amount written # Return amount written
return 0 return 0
def _format_card_file(self, card: Card) -> str:
tags = [self.favro_client.get_tag(tag_id).name for tag_id in card.tags]
assignments = [self.favro_client.get_user(assignment.user).name for assignment in card.assignments]
card_contents = CardContents(
card.name,
card.detailed_description,
tags,
assignments,
)
return self.formatter.format_card_contents(card_contents)
def splice(original_buffer: bytes, input_buffer: bytes, offset: int) -> bytes: def splice(original_buffer: bytes, input_buffer: bytes, offset: int) -> bytes:
return ( return (

View File

@ -19,7 +19,7 @@ class CardContents:
assignments: list[str] assignments: list[str]
class CardFileFormatter: class CardFileFormatter:
"""Component for formatting and parsing card files """ """Component for formatting and parsing card files."""
def __init__(self, obsidian_mode = True): def __init__(self, obsidian_mode = True):
self.obsidian_mode = obsidian_mode self.obsidian_mode = obsidian_mode
@ -90,5 +90,3 @@ class CardFileFormatter:
assignments = [], assignments = [],
) )
def format_card(self, card: Card) -> str:
return self.format_card_contents(CardContents(card.name, card.detailed_description))