Compare commits
No commits in common. "f12889ccdffbdfc091e8d9f7cf24b5a0ac37eebd" and "7aea826c60e8cae796ffcc40ce00bcfa31dc608d" have entirely different histories.
f12889ccdf
...
7aea826c60
3
.gitignore
vendored
3
.gitignore
vendored
@ -206,6 +206,3 @@ fabric.properties
|
|||||||
|
|
||||||
# Others
|
# Others
|
||||||
*.db
|
*.db
|
||||||
/v25pushx/
|
|
||||||
/report.sql
|
|
||||||
/vapidkeys.json
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
<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,6 +2,5 @@
|
|||||||
<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
19
LICENSE
@ -1,19 +0,0 @@
|
|||||||
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,3 +1,2 @@
|
|||||||
# v25
|
# v25
|
||||||
|
|
||||||
replace public keys in `dev-config.json` and `staging-config.json` with your own to allow messaging
|
|
||||||
|
@ -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[str, Union[str, Dict[str, Any]]]
|
_d_type = Dict[Any, Union[str, Dict[str, Any]]]
|
||||||
|
|
||||||
|
|
||||||
def get_config(file: str) -> _d_type:
|
def get_config() -> _d_type:
|
||||||
with open(file) as f:
|
with open('config.json') as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
@ -17,3 +17,11 @@ 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,7 +1,7 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||||
|
|
||||||
from v25 import config
|
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('dev-config.json')
|
d = config.get_config()
|
||||||
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-rc1',
|
version='0.0.1-a1',
|
||||||
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='',
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"db": "sqlite:///staging.db",
|
|
||||||
"subjects": {
|
|
||||||
"ro6ncuJxA_cGQ51hPKw11Q84of08j7GtOjL0Xr5GaFs=": {
|
|
||||||
"allowed": null,
|
|
||||||
"contacts": null
|
|
||||||
},
|
|
||||||
"oNNNAvX5nsJEQGf33xulhh27cpECgQtJT3jzu2VyNKY=": {
|
|
||||||
"allowed": null,
|
|
||||||
"contacts": null
|
|
||||||
},
|
|
||||||
"uLep1UFgMlYDaIM8MEgMYTDY6HWcUq6Y4VvkyglbGJ8=": {
|
|
||||||
"allowed": null,
|
|
||||||
"contacts": null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
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)
|
|
@ -1,7 +1,7 @@
|
|||||||
import nacl.hash
|
|
||||||
|
|
||||||
__all__ = ('Flags',)
|
__all__ = ('Flags',)
|
||||||
|
|
||||||
|
Q_FLAG = '<?>'
|
||||||
|
|
||||||
|
|
||||||
class Flags:
|
class Flags:
|
||||||
default: str
|
default: str
|
||||||
@ -9,17 +9,14 @@ class Flags:
|
|||||||
def __init__(self, flags: str):
|
def __init__(self, flags: str):
|
||||||
self.flags: str = flags
|
self.flags: str = flags
|
||||||
|
|
||||||
def hash(self):
|
def quable(self) -> bool:
|
||||||
return nacl.hash.sha256(self.flags.encode()).decode()
|
return Q_FLAG in self.flags
|
||||||
|
|
||||||
def joint(self, flags: str):
|
def deq(self) -> str:
|
||||||
return flags.startswith(self.hash())
|
return Flags(self.flags.replace(Q_FLAG, '')).deq() if self.quable() else self.flags
|
||||||
|
|
||||||
def join(self, flags: str):
|
def enq(self) -> str:
|
||||||
h = self.hash()
|
return self.flags if self.quable() else self.flags + Q_FLAG
|
||||||
if flags.startswith(h):
|
|
||||||
return flags
|
|
||||||
return h + flags
|
|
||||||
|
|
||||||
|
|
||||||
Flags.default = '<unedited>'
|
Flags.default = Flags('<unedited>').enq()
|
||||||
|
@ -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,
|
||||||
self.flags.replace('<unedited>', '<edited>')).sealed()
|
Flags(self.flags.replace('<unedited>', '<edited>')).enq()).sealed()
|
||||||
|
|
||||||
def edit_(self):
|
def edit_(self):
|
||||||
return self.flags_(self.flags)
|
return self.flags_(self.flags)
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
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
|
||||||
@ -146,43 +145,6 @@ 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
|
||||||
@ -191,7 +153,8 @@ 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:
|
||||||
with closing(self.Session()) as session:
|
session = self.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:
|
||||||
@ -206,23 +169,29 @@ 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:
|
||||||
with closing(self.Session()) as session:
|
session = self.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:
|
||||||
state = PushState.of(m.sto, session)
|
return
|
||||||
state.pushmsg = True
|
state = PushState.of(m.sto, session)
|
||||||
session.commit()
|
state.pushmsg = True
|
||||||
TypingState.reset(m, session)
|
session.commit()
|
||||||
|
finally:
|
||||||
|
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'
|
||||||
with closing(self.Session()) as session:
|
session = self.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)
|
||||||
@ -231,22 +200,27 @@ 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()
|
||||||
TypingState.reset(new, session)
|
finally:
|
||||||
|
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):
|
||||||
with closing(self.Session()) as session:
|
session = self.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 = {}
|
||||||
with closing(self.Session()) as session:
|
session = self.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(),
|
||||||
@ -258,51 +232,56 @@ 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']), sf=pair[0].vkey.encode())
|
query = query.filter_by(idn=Encoding.decode(params['exact']))
|
||||||
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'])
|
||||||
res = map(Msg.to_message, list(query.from_self().order_by(Msg.oid)))
|
return map(Msg.to_message, list(query.from_self().order_by(Msg.oid)))
|
||||||
if 'edit_' in params:
|
finally:
|
||||||
res = (m.edit_() for m in res)
|
session.close()
|
||||||
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):
|
||||||
with closing(self.Session()) as session:
|
assert not Flags(flags).quable(), 'flags misuse'
|
||||||
|
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).joint(flags)
|
assert Flags(msg.flags).quable(), 'flags misuse'
|
||||||
msg.flags = flags
|
msg.flags = flags
|
||||||
session.commit()
|
session.commit()
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
def clearsssj(self):
|
def clearsssj(self):
|
||||||
with closing(self.Session()) as session:
|
session = self.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"""
|
||||||
with closing(self.Session()) as session:
|
session = self.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):
|
||||||
@ -315,7 +294,8 @@ 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]]:
|
||||||
with closing(self.Session()) as session:
|
session = self.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)
|
||||||
@ -328,51 +308,67 @@ 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):
|
||||||
with closing(self.Session()) as session:
|
session = self.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):
|
||||||
with closing(self.Session()) as session:
|
session = self.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]:
|
||||||
with closing(self.Session()) as session:
|
session = self.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 not run(["node", "v25pushx/push.js"], input=json.dumps({
|
assert requests.post('http://localhost:5025/api/push', json={
|
||||||
"subscription": subscription,
|
"subscription": subscription,
|
||||||
"notification": {
|
"notification": {
|
||||||
"notification": {
|
"notification": {
|
||||||
"title": "New Message (V25PUSH)",
|
"title": "New Message (V25PUSH)"
|
||||||
"vibrate": [100, 50, 100],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), encoding='utf-8').returncode, "push subprocess failed"
|
}).status_code == 200, "push failed"
|
||||||
|
|
||||||
def pushpush(self, subject: Subject):
|
def pushpush(self, subject: Subject):
|
||||||
with closing(self.Session()) as session:
|
session = self.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):
|
||||||
with closing(self.Session()) as session:
|
session = self.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))
|
||||||
@ -394,7 +390,3 @@ 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,10 +33,6 @@ 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)
|
||||||
@ -48,7 +44,3 @@ 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,9 +21,6 @@ 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
|
||||||
|
|
||||||
@ -32,9 +29,6 @@ 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,9 +42,6 @@ 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})
|
||||||
|
|
||||||
@ -53,13 +50,6 @@ 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,11 +80,3 @@ 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