Message sending and receiving appears provisionally working. Job system next... :D
This commit is contained in:
@@ -165,7 +165,7 @@ class Message:
|
|||||||
def payload(self):
|
def payload(self):
|
||||||
if 'd' in self.data:
|
if 'd' in self.data:
|
||||||
pl = self.data['d']
|
pl = self.data['d']
|
||||||
if type(pl) in (dict, str, bytes):
|
if type(pl) in (dict, str, bytes, list):
|
||||||
return pl
|
return pl
|
||||||
else:
|
else:
|
||||||
return str(pl)
|
return str(pl)
|
||||||
@@ -173,11 +173,15 @@ class Message:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
@payload.setter
|
@payload.setter
|
||||||
def payload(self, payload: Union[str, bytes, dict]):
|
def payload(self, payload: Union[str, bytes, dict, list]):
|
||||||
if type(payload) in (str, bytes, dict):
|
logging.debug(f"Setting a message payload: {type(payload)}: {payload}")
|
||||||
|
if type(payload) in (str, bytes, dict, list):
|
||||||
|
logging.debug(f"Payload type is {type(payload)}, conversion to string unnecessary")
|
||||||
self.data['d'] = payload
|
self.data['d'] = payload
|
||||||
else:
|
else:
|
||||||
|
logging.debug("payload type is not in (str, bytes, dict, list); converting to string")
|
||||||
self.data['d'] = str(payload)
|
self.data['d'] = str(payload)
|
||||||
|
logging.debug(f"Final payload is: {type(payload)}: {payload}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def partial_unpack(cls, msg: dict) -> Self:
|
def partial_unpack(cls, msg: dict) -> Self:
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class Server:
|
|||||||
self.zeo_addr = None
|
self.zeo_addr = None
|
||||||
self.zeo_stop = None
|
self.zeo_stop = None
|
||||||
self.zeo = zeo
|
self.zeo = zeo
|
||||||
|
self.started = False
|
||||||
if data_dir:
|
if data_dir:
|
||||||
data_path = Path(data_dir)
|
data_path = Path(data_dir)
|
||||||
else:
|
else:
|
||||||
@@ -170,6 +171,13 @@ class Server:
|
|||||||
def register_path_handler(self, path_root: str, fn: Callable):
|
def register_path_handler(self, path_root: str, fn: Callable):
|
||||||
self.handlers[path_root.strip().lower()] = fn
|
self.handlers[path_root.strip().lower()] = fn
|
||||||
|
|
||||||
|
def server_worker(self):
|
||||||
|
"""When called, do things. Should get called every so often."""
|
||||||
|
if not self.started:
|
||||||
|
return
|
||||||
|
# Add things to do here:
|
||||||
|
pass
|
||||||
|
|
||||||
def start_db(self):
|
def start_db(self):
|
||||||
if not self.zeo:
|
if not self.zeo:
|
||||||
self.storage = ZODB.FileStorage.FileStorage(self.data_file)
|
self.storage = ZODB.FileStorage.FileStorage(self.data_file)
|
||||||
@@ -192,6 +200,11 @@ class Server:
|
|||||||
self.start_db()
|
self.start_db()
|
||||||
self.app.start(self.pe_server, self.pe_port)
|
self.app.start(self.pe_server, self.pe_port)
|
||||||
self.app.register_callsigns(self.callsign)
|
self.app.register_callsigns(self.callsign)
|
||||||
|
self.started = True
|
||||||
|
while self.started:
|
||||||
|
self.server_worker()
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
def exit_gracefully(self, signum, frame):
|
def exit_gracefully(self, signum, frame):
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class MessageAlreadySentError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class Message(persistent.Persistent):
|
class Message(persistent.Persistent):
|
||||||
def __init__(self, text: str, msg_to: Optional[Iterable[str],str]= None, msg_from: Optional[str] = None,
|
def __init__(self, text: str, msg_to: Optional[Iterable[str]]= None, msg_from: Optional[str] = None,
|
||||||
attachments: Optional[Iterable[Attachment]] = None):
|
attachments: Optional[Iterable[Attachment]] = None):
|
||||||
self.retrieved = False
|
self.retrieved = False
|
||||||
self.sent_at = datetime.datetime.now(datetime.UTC)
|
self.sent_at = datetime.datetime.now(datetime.UTC)
|
||||||
@@ -166,7 +166,7 @@ class Message(persistent.Persistent):
|
|||||||
"attachments": attachments,
|
"attachments": attachments,
|
||||||
"to": self.msg_to,
|
"to": self.msg_to,
|
||||||
"from": self.msg_from,
|
"from": self.msg_from,
|
||||||
"id": self.msg_id,
|
"id": str(self.msg_id),
|
||||||
"sent_at": self.sent_at.isoformat(),
|
"sent_at": self.sent_at.isoformat(),
|
||||||
"text": ""
|
"text": ""
|
||||||
}
|
}
|
||||||
@@ -175,6 +175,10 @@ class Message(persistent.Persistent):
|
|||||||
|
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> Self:
|
||||||
|
return Message(data['text'],msg_to=data.get('to'), attachments=data.get("attachments"))
|
||||||
|
|
||||||
def send(self, db: ZODB.DB) -> tuple:
|
def send(self, db: ZODB.DB) -> tuple:
|
||||||
if self.msg_delivered:
|
if self.msg_delivered:
|
||||||
raise MessageAlreadySentError("Cannot send a private message that has already been sent.")
|
raise MessageAlreadySentError("Cannot send a private message that has already been sent.")
|
||||||
@@ -221,6 +225,7 @@ DisplayOptions = namedtuple('DisplayOptions', ['get_text', 'limit', 'sort_by', '
|
|||||||
'get_attachments', 'sent_received_all'])
|
'get_attachments', 'sent_received_all'])
|
||||||
|
|
||||||
def parse_display_options(req: Request) -> DisplayOptions:
|
def parse_display_options(req: Request) -> DisplayOptions:
|
||||||
|
logging.debug(f"Parsing request vars for message get: {req.vars}")
|
||||||
sent_received_all = "received"
|
sent_received_all = "received"
|
||||||
d = req.vars.get("source")
|
d = req.vars.get("source")
|
||||||
if type(d) is str:
|
if type(d) is str:
|
||||||
@@ -234,7 +239,7 @@ def parse_display_options(req: Request) -> DisplayOptions:
|
|||||||
try:
|
try:
|
||||||
limit = int(limit)
|
limit = int(limit)
|
||||||
except:
|
except:
|
||||||
limit = 10
|
limit = None
|
||||||
|
|
||||||
d = req.vars.get('fetch_text')
|
d = req.vars.get('fetch_text')
|
||||||
if type(d) is str:
|
if type(d) is str:
|
||||||
@@ -245,12 +250,15 @@ def parse_display_options(req: Request) -> DisplayOptions:
|
|||||||
get_text = True
|
get_text = True
|
||||||
|
|
||||||
d = req.vars.get('fetch_attachments')
|
d = req.vars.get('fetch_attachments')
|
||||||
|
logging.debug(f"Parsing fetch_attachment var: {d}")
|
||||||
if type(d) is str:
|
if type(d) is str:
|
||||||
d.lower().strip()
|
d.lower().strip()
|
||||||
if d in yes_values:
|
if d in yes_values:
|
||||||
|
logging.debug("fetch_attachment is yes")
|
||||||
get_attachments = True
|
get_attachments = True
|
||||||
else:
|
else:
|
||||||
get_attachments = False
|
get_attachments = False
|
||||||
|
logging.debug("fetch_attachment is no")
|
||||||
|
|
||||||
r = req.vars.get('reverse')
|
r = req.vars.get('reverse')
|
||||||
if type(r) is str:
|
if type(r) is str:
|
||||||
@@ -274,9 +282,9 @@ def parse_display_options(req: Request) -> DisplayOptions:
|
|||||||
if type(s) is str:
|
if type(s) is str:
|
||||||
s = s.lower()
|
s = s.lower()
|
||||||
if s:
|
if s:
|
||||||
search = str(s)
|
search = str(s).lower()
|
||||||
|
|
||||||
return DisplayOptions(get_text, limit, sort_by, reverse, search, get_attachments, sent_receive_all)
|
return DisplayOptions(get_text, limit, sort_by, reverse, search, get_attachments, sent_received_all)
|
||||||
|
|
||||||
|
|
||||||
def handle_message_get(req: Request, conn: PacketServerConnection, db: ZODB.DB):
|
def handle_message_get(req: Request, conn: PacketServerConnection, db: ZODB.DB):
|
||||||
@@ -285,21 +293,52 @@ def handle_message_get(req: Request, conn: PacketServerConnection, db: ZODB.DB):
|
|||||||
msg_return = []
|
msg_return = []
|
||||||
with db.transaction() as db:
|
with db.transaction() as db:
|
||||||
mailbox_create(username, db.root())
|
mailbox_create(username, db.root())
|
||||||
mb = db.root.messages['username']
|
mb = db.root.messages[username]
|
||||||
messages = []
|
if opts.search:
|
||||||
if opts.reverse:
|
messages = [msg for msg in mb if (opts.search in msg.text.lower()) or (opts.search in msg.msg_to[0].lower())
|
||||||
for i in range(1,len(mb)+1):
|
or (opts.search in msg.msg_from.lower())]
|
||||||
messages.append(mb[len(mb) - 1])
|
|
||||||
else:
|
else:
|
||||||
for i in range(0,len(mb)):
|
messages = [msg for msg in mb]
|
||||||
messages.append(mb[i])
|
|
||||||
for msg in messages:
|
|
||||||
# do other filtering.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def object_root_handler(req: Request, conn: PacketServerConnection, db: ZODB.DB):
|
if opts.sort_by == "from":
|
||||||
|
messages.sort(key=lambda x: x.msg_from, reverse=opts.reverse)
|
||||||
|
elif opts.sort_by == "to":
|
||||||
|
messages.sort(key=lambda x: x.msg_to, reverse=opts.reverse)
|
||||||
|
else:
|
||||||
|
messages.sort(key=lambda x: x.sent_at, reverse=opts.reverse)
|
||||||
|
|
||||||
|
for i in range(0, len(messages)):
|
||||||
|
if opts.limit and (len(msg_return) >= opts.limit):
|
||||||
|
break
|
||||||
|
|
||||||
|
msg = messages[i]
|
||||||
|
msg.retrieved = True
|
||||||
|
msg_return.append(msg.to_dict(get_text=opts.get_text, get_attachments=opts.get_attachments))
|
||||||
|
|
||||||
|
response = Response.blank()
|
||||||
|
response.status_code = 200
|
||||||
|
response.payload = msg_return
|
||||||
|
send_response(conn, response, req)
|
||||||
|
|
||||||
|
def handle_message_post(req: Request, conn: PacketServerConnection, db: ZODB.DB):
|
||||||
|
username = ax25.Address(conn.remote_callsign).call.upper().strip()
|
||||||
|
try:
|
||||||
|
msg = Message.from_dict(req.payload)
|
||||||
|
except:
|
||||||
|
send_blank_response(conn, req, status_code=400)
|
||||||
|
logging.warning(f"User '{username}' attempted to post message with invalid payload: {req.payload}")
|
||||||
|
return
|
||||||
|
msg.msg_from = username
|
||||||
|
try:
|
||||||
|
send_counter, failed = msg.send(db)
|
||||||
|
except:
|
||||||
|
send_blank_response(conn, req, status_code=500)
|
||||||
|
logging.error(f"Error while attempting to send message:\n{format_exc()}")
|
||||||
|
return
|
||||||
|
|
||||||
|
send_blank_response(conn, req, status_code=201, payload={"successes": send_counter, "failed": failed})
|
||||||
|
|
||||||
|
def message_root_handler(req: Request, conn: PacketServerConnection, db: ZODB.DB):
|
||||||
logging.debug(f"{req} being processed by user_root_handler")
|
logging.debug(f"{req} being processed by user_root_handler")
|
||||||
if not user_authorized(conn, db):
|
if not user_authorized(conn, db):
|
||||||
logging.debug(f"user {conn.remote_callsign} not authorized")
|
logging.debug(f"user {conn.remote_callsign} not authorized")
|
||||||
@@ -308,6 +347,8 @@ def object_root_handler(req: Request, conn: PacketServerConnection, db: ZODB.DB)
|
|||||||
logging.debug("user is authorized")
|
logging.debug("user is authorized")
|
||||||
if req.method is Request.Method.GET:
|
if req.method is Request.Method.GET:
|
||||||
handle_message_get(req, conn, db)
|
handle_message_get(req, conn, db)
|
||||||
|
if req.method is Request.Method.POST:
|
||||||
|
handle_message_post(req, conn, db)
|
||||||
else:
|
else:
|
||||||
send_blank_response(conn, req, status_code=404)
|
send_blank_response(conn, req, status_code=404)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from packetserver.common import Message, Request, Response, PacketServerConnecti
|
|||||||
from .bulletin import bulletin_root_handler
|
from .bulletin import bulletin_root_handler
|
||||||
from .users import user_root_handler, user_authorized
|
from .users import user_root_handler, user_authorized
|
||||||
from .objects import object_root_handler
|
from .objects import object_root_handler
|
||||||
|
from .messages import message_root_handler
|
||||||
import logging
|
import logging
|
||||||
from typing import Union
|
from typing import Union
|
||||||
import ZODB
|
import ZODB
|
||||||
@@ -50,7 +51,8 @@ standard_handlers = {
|
|||||||
"": root_root_handler,
|
"": root_root_handler,
|
||||||
"bulletin": bulletin_root_handler,
|
"bulletin": bulletin_root_handler,
|
||||||
"user": user_root_handler,
|
"user": user_root_handler,
|
||||||
"object": object_root_handler
|
"object": object_root_handler,
|
||||||
|
"message": message_root_handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user