This commit is contained in:
AF 2020-11-08 17:42:40 +03:00
parent 41b1579785
commit 5f49cd7f46
5 changed files with 77 additions and 68 deletions

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/report.sql" dialect="GenericSQL" />
<file url="PROJECT" dialect="SQLite" />
</component>
</project>

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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']
))