From 5f49cd7f46824eaf70abefae5d72a7ee8a814db7 Mon Sep 17 00:00:00 2001 From: timotheyca Date: Sun, 8 Nov 2020 17:42:40 +0300 Subject: [PATCH] typing --- .idea/sqldialects.xml | 7 --- v25/storage/dbstorage.py | 123 +++++++++++++++++++------------------- v25/storage/secstorage.py | 4 ++ v25/storage/storage.py | 3 + v25/web/server/api.py | 8 +++ 5 files changed, 77 insertions(+), 68 deletions(-) delete mode 100644 .idea/sqldialects.xml diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml deleted file mode 100644 index 73155c4..0000000 --- a/.idea/sqldialects.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/v25/storage/dbstorage.py b/v25/storage/dbstorage.py index 62af5dc..6334f9f 100644 --- a/v25/storage/dbstorage.py +++ b/v25/storage/dbstorage.py @@ -1,4 +1,5 @@ import json +from contextlib import closing from subprocess import run from sys import stderr from threading import Thread @@ -145,6 +146,43 @@ class PushState(Base): return True +class TypingState(Base): + __tablename__ = 'typingstate' + + sf = Column(LargeBinary(crypto_sign_PUBLICKEYBYTES), primary_key=True) + st = Column(LargeBinary(crypto_sign_PUBLICKEYBYTES), primary_key=True) + last = Column(REAL) + + @classmethod + def _get(cls, sfrom: Subject, sto: Subject, session) -> Tuple['TypingState', 'TypingState']: + sf = sfrom.vkey.encode() + st = sto.vkey.encode() + try: + statef = session.query(TypingState).filter_by(sf=sf, st=st).one() + except NoResultFound: + statef = TypingState(sf=sf, st=st, last=0) + session.add(statef) + try: + statet = session.query(TypingState).filter_by(sf=st, st=sf).one() + except NoResultFound: + statet = TypingState(sf=st, st=sf, last=0) + session.add(statet) + return statef, statet + + @classmethod + def typing(cls, sfrom: Subject, sto: Subject, last: float, session) -> float: + statef, statet = cls._get(sfrom, sto, session) + statef.last = max(statef.last, last) + session.commit() + return statet.last + + @classmethod + def reset(cls, m: Message, session): + statef, _ = cls._get(m.sfrom, m.sto, session) + statef.last = 0 + session.commit() + + class DBStorage(PushStorage): def __init__(self, *args): self.args = args @@ -153,8 +191,7 @@ class DBStorage(PushStorage): self.Session = sessionmaker(bind=self.engine) def check(self, subject: Subject) -> dict: - session = self.Session() - try: + with closing(self.Session()) as session: sssj = session.query(SSSJ).filter_by(subject=subject.vkey.encode()).one_or_none() status = json.loads(sssj.status) if sssj else {} if 'contacts' in status: @@ -169,29 +206,23 @@ class DBStorage(PushStorage): contacts.add(st) status['contacts'] = list(map(Encoding.encode, contacts)) return status - finally: - session.close() def push(self, m: Message) -> None: - session = self.Session() - try: + with closing(self.Session()) as session: msg = Msg.from_message(m) session.add(msg) session.add(msg.sgn()) self.event(session, m, EVENT_PUSH) session.commit() - if m.sfrom == m.sto: - return - state = PushState.of(m.sto, session) - state.pushmsg = True - session.commit() - finally: - session.close() + if m.sfrom != m.sto: + state = PushState.of(m.sto, session) + state.pushmsg = True + session.commit() + TypingState.reset(m, session) def edit(self, old: Message, new: Message): assert old.edited(new), 'edit misuse' - session = self.Session() - try: + with closing(self.Session()) as session: msg = self.one_alike(session, old) assert msg.en == old.editnonce, 'edit misuse' msgn = Msg.from_message(new) @@ -200,27 +231,22 @@ class DBStorage(PushStorage): msg.flags = msgn.flags self.event(session, new, EVENT_EDIT) session.commit() - finally: - session.close() + TypingState.reset(new, session) @staticmethod def one_alike(session, m: Message) -> Msg: return session.query(Msg).filter_by(sf=m.sfrom.vkey.encode(), st=m.sto.vkey.encode(), idn=m.idnonce).one() def delete(self, m: Message): - session = self.Session() - try: + with closing(self.Session()) as session: session.delete(self.one_alike(session, m)) self.event(session, m, EVENT_DELETE) session.commit() - finally: - session.close() def pull(self, pair: Tuple[Subject, Subject], params: Optional[dict] = None) -> Iterable[Message]: if params is None: params = {} - session = self.Session() - try: + with closing(self.Session()) as session: cquery: Query = session.query(Msg).filter(or_( and_( Msg.sf == pair[0].vkey.encode(), @@ -249,39 +275,28 @@ class DBStorage(PushStorage): if 'limit' in params: query = query.limit(params['limit']) return map(Msg.to_message, list(query.from_self().order_by(Msg.oid))) - finally: - session.close() def flags(self, m: Message, flags: str): assert not Flags(flags).quable(), 'flags misuse' - session = self.Session() - try: + with closing(self.Session()) as session: msg: Msg = self.one_alike(session, m) assert msg.en == m.editnonce, 'flags misuse' assert Flags(msg.flags).quable(), 'flags misuse' msg.flags = flags session.commit() - finally: - session.close() def clearsssj(self): - session = self.Session() - try: + with closing(self.Session()) as session: session.query(SSSJ).delete() session.commit() - finally: - session.close() def ssssj(self, subject: str, status: str): """set SSSJ""" - session = self.Session() - try: + with closing(self.Session()) as session: subject = Subject(Encoding.decode(subject)).vkey.encode() session.query(SSSJ).filter_by(subject=subject).delete() session.add(SSSJ(subject=subject, status=status)) session.commit() - finally: - session.close() @staticmethod def event(session, m: Message, code: int): @@ -294,8 +309,7 @@ class DBStorage(PushStorage): return query.filter_by(en=en).one() def events(self, sfrom: Subject, sto: Subject, after: Optional[bytes]) -> Iterable[Tuple[bytes, bytes]]: - session = self.Session() - try: + with closing(self.Session()) as session: PushState.of(sto, session).online() session.commit() query: Query = session.query(MsgEvent) @@ -308,37 +322,26 @@ class DBStorage(PushStorage): query = query.order_by(MsgEvent.oid) ev: MsgEvent return [(ev.en, ev.idn) for ev in query.all()] - finally: - session.close() def cleanevents(self): - session = self.Session() - try: + with closing(self.Session()) as session: session.query(MsgEvent).delete(MsgEvent.ts < time() - 604800) session.commit() - finally: - session.close() def subscribe(self, subject: Subject, subscription: dict): - session = self.Session() - try: + with closing(self.Session()) as session: subject = subject.vkey.encode() session.query(PushSub).filter_by(subject=subject).delete() session.add(PushSub(subject=subject, subscription=json.dumps(subscription))) session.commit() - finally: - session.close() def subscription(self, subject: Subject) -> Optional[dict]: - session = self.Session() - try: + with closing(self.Session()) as session: subscription: Optional[PushSub] = session.query(PushSub).filter_by( subject=subject.vkey.encode()).one_or_none() if subscription is None: return None return json.loads(subscription.subscription) - finally: - session.close() @staticmethod def pushsubscription(subscription: dict): @@ -353,23 +356,17 @@ class DBStorage(PushStorage): }), encoding='utf-8').returncode, "push subprocess failed" def pushpush(self, subject: Subject): - session = self.Session() - try: + with closing(self.Session()) as session: notify = PushState.of(subject, session).notify() session.commit() - finally: - session.close() if notify: subscription = self.subscription(subject) if subscription: self.pushsubscription(subscription) def push_once(self): - session = self.Session() - try: + with closing(self.Session()) as session: subs: List[PushSub] = session.query(PushSub).all() - finally: - session.close() for sub in subs: try: self.pushpush(Subject(sub.subject)) @@ -391,3 +388,7 @@ class DBStorage(PushStorage): def pushing(self): self.push_forever() return self + + def typing(self, sfrom: Subject, sto: Subject, last: float) -> float: + with closing(self.Session()) as session: + return TypingState.typing(sfrom, sto, last, session) diff --git a/v25/storage/secstorage.py b/v25/storage/secstorage.py index 92e39a6..3bd7154 100644 --- a/v25/storage/secstorage.py +++ b/v25/storage/secstorage.py @@ -44,3 +44,7 @@ class SecureStorage(PushStorage): def subscribe(self, subject: Subject, subscription: dict): assert self.subject == subject, self.asrt() return self.storage.subscribe(subject, subscription) + + def typing(self, sfrom: Subject, sto: Subject, last: float) -> float: + assert self.subject == sfrom, self.asrt() + return self.storage.typing(sfrom, sto, last) diff --git a/v25/storage/storage.py b/v25/storage/storage.py index 29ded43..d98fcac 100644 --- a/v25/storage/storage.py +++ b/v25/storage/storage.py @@ -29,6 +29,9 @@ class EventStorage(AbstractStorage, ABC): def events(self, sfrom: Subject, sto: Subject, after: Optional[bytes]) -> Iterable[Tuple[bytes, bytes]]: raise NotImplementedError + def typing(self, sfrom: Subject, sto: Subject, last: float) -> float: + raise NotImplementedError + EVENT_PUSH = 0 EVENT_EDIT = 1 diff --git a/v25/web/server/api.py b/v25/web/server/api.py index d19ad79..7db5895 100644 --- a/v25/web/server/api.py +++ b/v25/web/server/api.py @@ -80,3 +80,11 @@ class API(Flask): def subscribe(): return self.nomessassertcall(lambda d, storage: storage.subscribe(Subject.loads(d['subject']), d['subscription'])) + + @app.route('/typing', methods=['POST']) + def typing(): + return self.nomessassertcall(lambda d, storage: storage.typing( + Subject.loads(d['sfrom']), + Subject.loads(d['sto']), + d['last'] + ))