Compare commits
10 Commits
7aea826c60
...
f12889ccdf
Author | SHA1 | Date | |
---|---|---|---|
|
f12889ccdf | ||
da1d9a23d6 | |||
5f49cd7f46 | |||
41b1579785 | |||
c9d8be1a24 | |||
f008ed2462 | |||
ee4a6865d8 | |||
ab311c54d8 | |||
57fc83567d | |||
502afe9827 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -206,3 +206,6 @@ fabric.properties
|
|||||||
|
|
||||||
# Others
|
# Others
|
||||||
*.db
|
*.db
|
||||||
|
/v25pushx/
|
||||||
|
/report.sql
|
||||||
|
/vapidkeys.json
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/v25pushx" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
@ -2,5 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/v25pushx" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
19
LICENSE
Normal file
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2021 PARRRATE T&V
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -1,2 +1,3 @@
|
|||||||
# v25
|
# v25
|
||||||
|
|
||||||
|
replace public keys in `dev-config.json` and `staging-config.json` with your own to allow messaging
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||||
|
|
||||||
import config
|
from v25 import config
|
||||||
from v25.storage.dbstorage import DBStorage
|
from v25.storage.dbstorage import DBStorage
|
||||||
from v25.web.server.api import API
|
from v25.web.server.api import API
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ def simple(_env, resp):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
d = config.get_config()
|
d = config.get_config('dev-config.json')
|
||||||
config.from_config(d)
|
config.from_config(d)
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='v25',
|
name='v25',
|
||||||
version='0.0.1-a1',
|
version='0.0.1-rc1',
|
||||||
packages=['v25', 'v25.web', 'v25.web.client', 'v25.web.server', 'v25.storage', 'v25.messaging'],
|
packages=['v25', 'v25.web', 'v25.web.client', 'v25.web.server', 'v25.storage', 'v25.messaging'],
|
||||||
url='',
|
url='',
|
||||||
license='',
|
license='',
|
||||||
|
17
staging-config.json
Normal file
17
staging-config.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"db": "sqlite:///staging.db",
|
||||||
|
"subjects": {
|
||||||
|
"ro6ncuJxA_cGQ51hPKw11Q84of08j7GtOjL0Xr5GaFs=": {
|
||||||
|
"allowed": null,
|
||||||
|
"contacts": null
|
||||||
|
},
|
||||||
|
"oNNNAvX5nsJEQGf33xulhh27cpECgQtJT3jzu2VyNKY=": {
|
||||||
|
"allowed": null,
|
||||||
|
"contacts": null
|
||||||
|
},
|
||||||
|
"uLep1UFgMlYDaIM8MEgMYTDY6HWcUq6Y4VvkyglbGJ8=": {
|
||||||
|
"allowed": null,
|
||||||
|
"contacts": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
staging-main.py
Normal file
22
staging-main.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||||
|
|
||||||
|
from v25 import config
|
||||||
|
from v25.storage.dbstorage import DBStorage
|
||||||
|
from v25.web.server.api import API
|
||||||
|
|
||||||
|
|
||||||
|
def simple(_env, resp):
|
||||||
|
resp('404 OK', [])
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
d = config.get_config('staging-config.json')
|
||||||
|
config.from_config(d)
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.wsgi_app = DispatcherMiddleware(simple, {
|
||||||
|
'/v25': API(__name__, DBStorage(d['db']).pushing())
|
||||||
|
})
|
||||||
|
app.config['ENV'] = 'staging'
|
||||||
|
app.run(port=5013)
|
@ -4,11 +4,11 @@ from typing import Dict, Any, Union
|
|||||||
from v25.storage.dbstorage import DBStorage
|
from v25.storage.dbstorage import DBStorage
|
||||||
|
|
||||||
|
|
||||||
_d_type = Dict[Any, Union[str, Dict[str, Any]]]
|
_d_type = Dict[str, Union[str, Dict[str, Any]]]
|
||||||
|
|
||||||
|
|
||||||
def get_config() -> _d_type:
|
def get_config(file: str) -> _d_type:
|
||||||
with open('config.json') as f:
|
with open(file) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
@ -17,11 +17,3 @@ def from_config(d: _d_type):
|
|||||||
subjects = d["subjects"]
|
subjects = d["subjects"]
|
||||||
for subject in subjects:
|
for subject in subjects:
|
||||||
storage.ssssj(subject, json.dumps(subjects[subject]))
|
storage.ssssj(subject, json.dumps(subjects[subject]))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
from_config(get_config())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,6 +1,6 @@
|
|||||||
__all__ = ('Flags',)
|
import nacl.hash
|
||||||
|
|
||||||
Q_FLAG = '<?>'
|
__all__ = ('Flags',)
|
||||||
|
|
||||||
|
|
||||||
class Flags:
|
class Flags:
|
||||||
@ -9,14 +9,17 @@ class Flags:
|
|||||||
def __init__(self, flags: str):
|
def __init__(self, flags: str):
|
||||||
self.flags: str = flags
|
self.flags: str = flags
|
||||||
|
|
||||||
def quable(self) -> bool:
|
def hash(self):
|
||||||
return Q_FLAG in self.flags
|
return nacl.hash.sha256(self.flags.encode()).decode()
|
||||||
|
|
||||||
def deq(self) -> str:
|
def joint(self, flags: str):
|
||||||
return Flags(self.flags.replace(Q_FLAG, '')).deq() if self.quable() else self.flags
|
return flags.startswith(self.hash())
|
||||||
|
|
||||||
def enq(self) -> str:
|
def join(self, flags: str):
|
||||||
return self.flags if self.quable() else self.flags + Q_FLAG
|
h = self.hash()
|
||||||
|
if flags.startswith(h):
|
||||||
|
return flags
|
||||||
|
return h + flags
|
||||||
|
|
||||||
|
|
||||||
Flags.default = Flags('<unedited>').enq()
|
Flags.default = '<unedited>'
|
||||||
|
@ -95,7 +95,7 @@ class Message:
|
|||||||
def edit(self, pcontent: bytes) -> 'Message':
|
def edit(self, pcontent: bytes) -> 'Message':
|
||||||
return Message(self.sfrom, self.sto, self.idnonce, None,
|
return Message(self.sfrom, self.sto, self.idnonce, None,
|
||||||
Encoding.nonce(), pcontent, None,
|
Encoding.nonce(), pcontent, None,
|
||||||
Flags(self.flags.replace('<unedited>', '<edited>')).enq()).sealed()
|
self.flags.replace('<unedited>', '<edited>')).sealed()
|
||||||
|
|
||||||
def edit_(self):
|
def edit_(self):
|
||||||
return self.flags_(self.flags)
|
return self.flags_(self.flags)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
|
from contextlib import closing
|
||||||
|
from subprocess import run
|
||||||
from sys import stderr
|
from sys import stderr
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from time import time, sleep
|
from time import time, sleep
|
||||||
from typing import Tuple, Optional, Iterable, List
|
from typing import Tuple, Optional, Iterable, List
|
||||||
|
|
||||||
import requests
|
|
||||||
from nacl.bindings import crypto_sign_PUBLICKEYBYTES
|
from nacl.bindings import crypto_sign_PUBLICKEYBYTES
|
||||||
from sqlalchemy import create_engine, LargeBinary, Column, REAL, BLOB, String, or_, and_, ForeignKeyConstraint, \
|
from sqlalchemy import create_engine, LargeBinary, Column, REAL, BLOB, String, or_, and_, ForeignKeyConstraint, \
|
||||||
Integer, UniqueConstraint, Index, Boolean
|
Integer, UniqueConstraint, Index, Boolean
|
||||||
@ -145,6 +146,43 @@ class PushState(Base):
|
|||||||
return True
|
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):
|
class DBStorage(PushStorage):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
self.args = args
|
self.args = args
|
||||||
@ -153,8 +191,7 @@ class DBStorage(PushStorage):
|
|||||||
self.Session = sessionmaker(bind=self.engine)
|
self.Session = sessionmaker(bind=self.engine)
|
||||||
|
|
||||||
def check(self, subject: Subject) -> dict:
|
def check(self, subject: Subject) -> dict:
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
sssj = session.query(SSSJ).filter_by(subject=subject.vkey.encode()).one_or_none()
|
sssj = session.query(SSSJ).filter_by(subject=subject.vkey.encode()).one_or_none()
|
||||||
status = json.loads(sssj.status) if sssj else {}
|
status = json.loads(sssj.status) if sssj else {}
|
||||||
if 'contacts' in status:
|
if 'contacts' in status:
|
||||||
@ -169,29 +206,23 @@ class DBStorage(PushStorage):
|
|||||||
contacts.add(st)
|
contacts.add(st)
|
||||||
status['contacts'] = list(map(Encoding.encode, contacts))
|
status['contacts'] = list(map(Encoding.encode, contacts))
|
||||||
return status
|
return status
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def push(self, m: Message) -> None:
|
def push(self, m: Message) -> None:
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
msg = Msg.from_message(m)
|
msg = Msg.from_message(m)
|
||||||
session.add(msg)
|
session.add(msg)
|
||||||
session.add(msg.sgn())
|
session.add(msg.sgn())
|
||||||
self.event(session, m, EVENT_PUSH)
|
self.event(session, m, EVENT_PUSH)
|
||||||
session.commit()
|
session.commit()
|
||||||
if m.sfrom == m.sto:
|
if m.sfrom != m.sto:
|
||||||
return
|
|
||||||
state = PushState.of(m.sto, session)
|
state = PushState.of(m.sto, session)
|
||||||
state.pushmsg = True
|
state.pushmsg = True
|
||||||
session.commit()
|
session.commit()
|
||||||
finally:
|
TypingState.reset(m, session)
|
||||||
session.close()
|
|
||||||
|
|
||||||
def edit(self, old: Message, new: Message):
|
def edit(self, old: Message, new: Message):
|
||||||
assert old.edited(new), 'edit misuse'
|
assert old.edited(new), 'edit misuse'
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
msg = self.one_alike(session, old)
|
msg = self.one_alike(session, old)
|
||||||
assert msg.en == old.editnonce, 'edit misuse'
|
assert msg.en == old.editnonce, 'edit misuse'
|
||||||
msgn = Msg.from_message(new)
|
msgn = Msg.from_message(new)
|
||||||
@ -200,27 +231,22 @@ class DBStorage(PushStorage):
|
|||||||
msg.flags = msgn.flags
|
msg.flags = msgn.flags
|
||||||
self.event(session, new, EVENT_EDIT)
|
self.event(session, new, EVENT_EDIT)
|
||||||
session.commit()
|
session.commit()
|
||||||
finally:
|
TypingState.reset(new, session)
|
||||||
session.close()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def one_alike(session, m: Message) -> Msg:
|
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()
|
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):
|
def delete(self, m: Message):
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
session.delete(self.one_alike(session, m))
|
session.delete(self.one_alike(session, m))
|
||||||
self.event(session, m, EVENT_DELETE)
|
self.event(session, m, EVENT_DELETE)
|
||||||
session.commit()
|
session.commit()
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def pull(self, pair: Tuple[Subject, Subject], params: Optional[dict] = None) -> Iterable[Message]:
|
def pull(self, pair: Tuple[Subject, Subject], params: Optional[dict] = None) -> Iterable[Message]:
|
||||||
if params is None:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
cquery: Query = session.query(Msg).filter(or_(
|
cquery: Query = session.query(Msg).filter(or_(
|
||||||
and_(
|
and_(
|
||||||
Msg.sf == pair[0].vkey.encode(),
|
Msg.sf == pair[0].vkey.encode(),
|
||||||
@ -232,56 +258,51 @@ class DBStorage(PushStorage):
|
|||||||
),
|
),
|
||||||
))
|
))
|
||||||
query: Query = cquery
|
query: Query = cquery
|
||||||
if 'ts' in params:
|
|
||||||
if '>' in params:
|
|
||||||
query = query.filter(Msg.ts > params['ts']['>'])
|
|
||||||
if '<' in params:
|
|
||||||
query = query.filter(Msg.ts < params['ts']['<'])
|
|
||||||
if params.get('before'):
|
if params.get('before'):
|
||||||
query = query.filter(Msg.oid < cquery.filter(Msg.idn == Encoding.decode(params['before'])).one().oid)
|
query = query.filter(Msg.oid < cquery.filter(Msg.idn == Encoding.decode(params['before'])).one().oid)
|
||||||
if params.get('after'):
|
if params.get('after'):
|
||||||
query = query.filter(Msg.oid > cquery.filter(Msg.idn == Encoding.decode(params['after'])).one().oid)
|
query = query.filter(Msg.oid > cquery.filter(Msg.idn == Encoding.decode(params['after'])).one().oid)
|
||||||
if params.get('exact'):
|
if params.get('exact'):
|
||||||
query = query.filter_by(idn=Encoding.decode(params['exact']))
|
query = query.filter_by(idn=Encoding.decode(params['exact']), sf=pair[0].vkey.encode())
|
||||||
for flag in params.get('flags', ()):
|
for flag in params.get('flags', ()):
|
||||||
query = query.filter(Msg.flags.contains(flag))
|
query = query.filter(Msg.flags.contains(flag))
|
||||||
query = query.order_by(Msg.oid.desc())
|
query = query.order_by(Msg.oid.desc())
|
||||||
if 'limit' in params:
|
if 'limit' in params:
|
||||||
query = query.limit(params['limit'])
|
query = query.limit(params['limit'])
|
||||||
return map(Msg.to_message, list(query.from_self().order_by(Msg.oid)))
|
res = map(Msg.to_message, list(query.from_self().order_by(Msg.oid)))
|
||||||
finally:
|
if 'edit_' in params:
|
||||||
session.close()
|
res = (m.edit_() for m in res)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def exact(self, sfrom: Subject, sto: Subject, idnonce: bytes, editnonce: Optional[bytes]) -> Optional[Message]:
|
||||||
|
with closing(self.Session()) as session:
|
||||||
|
query: Query = session.query(Msg)
|
||||||
|
query = query.filter_by(sf=sfrom, st=sto, idn=idnonce)
|
||||||
|
if editnonce:
|
||||||
|
query = query.filter_by(en=editnonce)
|
||||||
|
msg: Optional[Msg] = query.one_or_none()
|
||||||
|
return msg or msg.to_message()
|
||||||
|
|
||||||
def flags(self, m: Message, flags: str):
|
def flags(self, m: Message, flags: str):
|
||||||
assert not Flags(flags).quable(), 'flags misuse'
|
with closing(self.Session()) as session:
|
||||||
session = self.Session()
|
|
||||||
try:
|
|
||||||
msg: Msg = self.one_alike(session, m)
|
msg: Msg = self.one_alike(session, m)
|
||||||
assert msg.en == m.editnonce, 'flags misuse'
|
assert msg.en == m.editnonce, 'flags misuse'
|
||||||
assert Flags(msg.flags).quable(), 'flags misuse'
|
assert Flags(msg.flags).joint(flags)
|
||||||
msg.flags = flags
|
msg.flags = flags
|
||||||
session.commit()
|
session.commit()
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def clearsssj(self):
|
def clearsssj(self):
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
session.query(SSSJ).delete()
|
session.query(SSSJ).delete()
|
||||||
session.commit()
|
session.commit()
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def ssssj(self, subject: str, status: str):
|
def ssssj(self, subject: str, status: str):
|
||||||
"""set SSSJ"""
|
"""set SSSJ"""
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
subject = Subject(Encoding.decode(subject)).vkey.encode()
|
subject = Subject(Encoding.decode(subject)).vkey.encode()
|
||||||
session.query(SSSJ).filter_by(subject=subject).delete()
|
session.query(SSSJ).filter_by(subject=subject).delete()
|
||||||
session.add(SSSJ(subject=subject, status=status))
|
session.add(SSSJ(subject=subject, status=status))
|
||||||
session.commit()
|
session.commit()
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def event(session, m: Message, code: int):
|
def event(session, m: Message, code: int):
|
||||||
@ -294,8 +315,7 @@ class DBStorage(PushStorage):
|
|||||||
return query.filter_by(en=en).one()
|
return query.filter_by(en=en).one()
|
||||||
|
|
||||||
def events(self, sfrom: Subject, sto: Subject, after: Optional[bytes]) -> Iterable[Tuple[bytes, bytes]]:
|
def events(self, sfrom: Subject, sto: Subject, after: Optional[bytes]) -> Iterable[Tuple[bytes, bytes]]:
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
PushState.of(sto, session).online()
|
PushState.of(sto, session).online()
|
||||||
session.commit()
|
session.commit()
|
||||||
query: Query = session.query(MsgEvent)
|
query: Query = session.query(MsgEvent)
|
||||||
@ -308,67 +328,51 @@ class DBStorage(PushStorage):
|
|||||||
query = query.order_by(MsgEvent.oid)
|
query = query.order_by(MsgEvent.oid)
|
||||||
ev: MsgEvent
|
ev: MsgEvent
|
||||||
return [(ev.en, ev.idn) for ev in query.all()]
|
return [(ev.en, ev.idn) for ev in query.all()]
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def cleanevents(self):
|
def cleanevents(self):
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
session.query(MsgEvent).delete(MsgEvent.ts < time() - 604800)
|
session.query(MsgEvent).delete(MsgEvent.ts < time() - 604800)
|
||||||
session.commit()
|
session.commit()
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def subscribe(self, subject: Subject, subscription: dict):
|
def subscribe(self, subject: Subject, subscription: dict):
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
subject = subject.vkey.encode()
|
subject = subject.vkey.encode()
|
||||||
session.query(PushSub).filter_by(subject=subject).delete()
|
session.query(PushSub).filter_by(subject=subject).delete()
|
||||||
session.add(PushSub(subject=subject, subscription=json.dumps(subscription)))
|
session.add(PushSub(subject=subject, subscription=json.dumps(subscription)))
|
||||||
session.commit()
|
session.commit()
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
def subscription(self, subject: Subject) -> Optional[dict]:
|
def subscription(self, subject: Subject) -> Optional[dict]:
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
subscription: Optional[PushSub] = session.query(PushSub).filter_by(
|
subscription: Optional[PushSub] = session.query(PushSub).filter_by(
|
||||||
subject=subject.vkey.encode()).one_or_none()
|
subject=subject.vkey.encode()).one_or_none()
|
||||||
if subscription is None:
|
if subscription is None:
|
||||||
return None
|
return None
|
||||||
return json.loads(subscription.subscription)
|
return json.loads(subscription.subscription)
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def pushsubscription(subscription: dict):
|
def pushsubscription(subscription: dict):
|
||||||
assert requests.post('http://localhost:5025/api/push', json={
|
assert not run(["node", "v25pushx/push.js"], input=json.dumps({
|
||||||
"subscription": subscription,
|
"subscription": subscription,
|
||||||
"notification": {
|
"notification": {
|
||||||
"notification": {
|
"notification": {
|
||||||
"title": "New Message (V25PUSH)"
|
"title": "New Message (V25PUSH)",
|
||||||
|
"vibrate": [100, 50, 100],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).status_code == 200, "push failed"
|
}), encoding='utf-8').returncode, "push subprocess failed"
|
||||||
|
|
||||||
def pushpush(self, subject: Subject):
|
def pushpush(self, subject: Subject):
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
notify = PushState.of(subject, session).notify()
|
notify = PushState.of(subject, session).notify()
|
||||||
session.commit()
|
session.commit()
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
if notify:
|
if notify:
|
||||||
subscription = self.subscription(subject)
|
subscription = self.subscription(subject)
|
||||||
if subscription:
|
if subscription:
|
||||||
self.pushsubscription(subscription)
|
self.pushsubscription(subscription)
|
||||||
|
|
||||||
def push_once(self):
|
def push_once(self):
|
||||||
session = self.Session()
|
with closing(self.Session()) as session:
|
||||||
try:
|
|
||||||
subs: List[PushSub] = session.query(PushSub).all()
|
subs: List[PushSub] = session.query(PushSub).all()
|
||||||
finally:
|
|
||||||
session.close()
|
|
||||||
for sub in subs:
|
for sub in subs:
|
||||||
try:
|
try:
|
||||||
self.pushpush(Subject(sub.subject))
|
self.pushpush(Subject(sub.subject))
|
||||||
@ -390,3 +394,7 @@ class DBStorage(PushStorage):
|
|||||||
def pushing(self):
|
def pushing(self):
|
||||||
self.push_forever()
|
self.push_forever()
|
||||||
return self
|
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)
|
||||||
|
@ -33,6 +33,10 @@ class SecureStorage(PushStorage):
|
|||||||
assert self.subject in pair, self.asrt()
|
assert self.subject in pair, self.asrt()
|
||||||
return self.storage.pull(pair, params)
|
return self.storage.pull(pair, params)
|
||||||
|
|
||||||
|
def exact(self, sfrom: Subject, sto: Subject, idnonce: bytes, editnonce: Optional[bytes]) -> Optional[Message]:
|
||||||
|
assert self.subject in (sfrom, sto)
|
||||||
|
return self.storage.exact(sfrom, sto, idnonce, editnonce)
|
||||||
|
|
||||||
def flags(self, m: Message, flags: str):
|
def flags(self, m: Message, flags: str):
|
||||||
assert self.subject in m.pair, self.asrt()
|
assert self.subject in m.pair, self.asrt()
|
||||||
return self.storage.flags(m, flags)
|
return self.storage.flags(m, flags)
|
||||||
@ -44,3 +48,7 @@ class SecureStorage(PushStorage):
|
|||||||
def subscribe(self, subject: Subject, subscription: dict):
|
def subscribe(self, subject: Subject, subscription: dict):
|
||||||
assert self.subject == subject, self.asrt()
|
assert self.subject == subject, self.asrt()
|
||||||
return self.storage.subscribe(subject, subscription)
|
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)
|
||||||
|
@ -21,6 +21,9 @@ class AbstractStorage(ABC):
|
|||||||
def pull(self, pair: Tuple[Subject, Subject], params: Optional[dict] = None) -> Iterable[Message]:
|
def pull(self, pair: Tuple[Subject, Subject], params: Optional[dict] = None) -> Iterable[Message]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def exact(self, sfrom: Subject, sto: Subject, idnonce: bytes, editnonce: Optional[bytes]) -> Optional[Message]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def flags(self, m: Message, flags: str):
|
def flags(self, m: Message, flags: str):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -29,6 +32,9 @@ class EventStorage(AbstractStorage, ABC):
|
|||||||
def events(self, sfrom: Subject, sto: Subject, after: Optional[bytes]) -> Iterable[Tuple[bytes, bytes]]:
|
def events(self, sfrom: Subject, sto: Subject, after: Optional[bytes]) -> Iterable[Tuple[bytes, bytes]]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def typing(self, sfrom: Subject, sto: Subject, last: float) -> float:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
EVENT_PUSH = 0
|
EVENT_PUSH = 0
|
||||||
EVENT_EDIT = 1
|
EVENT_EDIT = 1
|
||||||
|
@ -42,6 +42,9 @@ class RemoteStorage(PushStorage):
|
|||||||
return map(Message.loads,
|
return map(Message.loads,
|
||||||
self.req('pull', {'params': params, 'pair': [subject.dumps() for subject in pair]}))
|
self.req('pull', {'params': params, 'pair': [subject.dumps() for subject in pair]}))
|
||||||
|
|
||||||
|
def exact(self, sfrom: Subject, sto: Subject, idnonce: bytes, editnonce: Optional[bytes]) -> Optional[Message]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def flags(self, m: Message, flags: str):
|
def flags(self, m: Message, flags: str):
|
||||||
self.req('flags', {'m': m.dumps(), 'flags': flags})
|
self.req('flags', {'m': m.dumps(), 'flags': flags})
|
||||||
|
|
||||||
@ -50,6 +53,13 @@ class RemoteStorage(PushStorage):
|
|||||||
self.req('events', {'sfrom': sfrom.dumps(), 'sto': sto.dumps(),
|
self.req('events', {'sfrom': sfrom.dumps(), 'sto': sto.dumps(),
|
||||||
'after': Encoding.encode(after)})]
|
'after': Encoding.encode(after)})]
|
||||||
|
|
||||||
|
def typing(self, sfrom: Subject, sto: Subject, last: float) -> float:
|
||||||
|
return self.req('typing', {
|
||||||
|
'sfrom': sfrom.dumps(),
|
||||||
|
'sto': sto.dumps(),
|
||||||
|
'last': last
|
||||||
|
})
|
||||||
|
|
||||||
def subscribe(self, subject: Subject, subscription: dict):
|
def subscribe(self, subject: Subject, subscription: dict):
|
||||||
self.req('subscribe', {
|
self.req('subscribe', {
|
||||||
'subject': subject.dumps(),
|
'subject': subject.dumps(),
|
||||||
|
@ -80,3 +80,11 @@ class API(Flask):
|
|||||||
def subscribe():
|
def subscribe():
|
||||||
return self.nomessassertcall(lambda d, storage: storage.subscribe(Subject.loads(d['subject']),
|
return self.nomessassertcall(lambda d, storage: storage.subscribe(Subject.loads(d['subject']),
|
||||||
d['subscription']))
|
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']
|
||||||
|
))
|
||||||
|
Loading…
Reference in New Issue
Block a user