diff --git a/src/packetserver/common/__init__.py b/src/packetserver/common/__init__.py index 78913f9..98aede6 100644 --- a/src/packetserver/common/__init__.py +++ b/src/packetserver/common/__init__.py @@ -165,7 +165,7 @@ class Message: def payload(self): if 'd' in self.data: pl = self.data['d'] - if type(pl) in (dict, str, bytes): + if type(pl) in (dict, str, bytes, list): return pl else: return str(pl) @@ -173,11 +173,15 @@ class Message: return "" @payload.setter - def payload(self, payload: Union[str, bytes, dict]): - if type(payload) in (str, bytes, dict): + def payload(self, payload: Union[str, bytes, dict, list]): + 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 else: + logging.debug("payload type is not in (str, bytes, dict, list); converting to string") self.data['d'] = str(payload) + logging.debug(f"Final payload is: {type(payload)}: {payload}") @classmethod def partial_unpack(cls, msg: dict) -> Self: diff --git a/src/packetserver/server/__init__.py b/src/packetserver/server/__init__.py index 0bd505e..3f5ab85 100644 --- a/src/packetserver/server/__init__.py +++ b/src/packetserver/server/__init__.py @@ -36,6 +36,7 @@ class Server: self.zeo_addr = None self.zeo_stop = None self.zeo = zeo + self.started = False if data_dir: data_path = Path(data_dir) else: @@ -170,6 +171,13 @@ class Server: def register_path_handler(self, path_root: str, fn: Callable): 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): if not self.zeo: self.storage = ZODB.FileStorage.FileStorage(self.data_file) @@ -192,6 +200,11 @@ class Server: self.start_db() self.app.start(self.pe_server, self.pe_port) self.app.register_callsigns(self.callsign) + self.started = True + while self.started: + self.server_worker() + time.sleep(5) + def exit_gracefully(self, signum, frame): self.stop() diff --git a/src/packetserver/server/messages.py b/src/packetserver/server/messages.py index 5dbafbd..528dc41 100644 --- a/src/packetserver/server/messages.py +++ b/src/packetserver/server/messages.py @@ -120,7 +120,7 @@ class MessageAlreadySentError(Exception): pass 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): self.retrieved = False self.sent_at = datetime.datetime.now(datetime.UTC) @@ -166,7 +166,7 @@ class Message(persistent.Persistent): "attachments": attachments, "to": self.msg_to, "from": self.msg_from, - "id": self.msg_id, + "id": str(self.msg_id), "sent_at": self.sent_at.isoformat(), "text": "" } @@ -175,6 +175,10 @@ class Message(persistent.Persistent): 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: if self.msg_delivered: 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']) def parse_display_options(req: Request) -> DisplayOptions: + logging.debug(f"Parsing request vars for message get: {req.vars}") sent_received_all = "received" d = req.vars.get("source") if type(d) is str: @@ -234,7 +239,7 @@ def parse_display_options(req: Request) -> DisplayOptions: try: limit = int(limit) except: - limit = 10 + limit = None d = req.vars.get('fetch_text') if type(d) is str: @@ -245,12 +250,15 @@ def parse_display_options(req: Request) -> DisplayOptions: get_text = True d = req.vars.get('fetch_attachments') + logging.debug(f"Parsing fetch_attachment var: {d}") if type(d) is str: d.lower().strip() if d in yes_values: + logging.debug("fetch_attachment is yes") get_attachments = True else: get_attachments = False + logging.debug("fetch_attachment is no") r = req.vars.get('reverse') if type(r) is str: @@ -274,9 +282,9 @@ def parse_display_options(req: Request) -> DisplayOptions: if type(s) is str: s = s.lower() 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): @@ -285,21 +293,52 @@ def handle_message_get(req: Request, conn: PacketServerConnection, db: ZODB.DB): msg_return = [] with db.transaction() as db: mailbox_create(username, db.root()) - mb = db.root.messages['username'] - messages = [] - if opts.reverse: - for i in range(1,len(mb)+1): - messages.append(mb[len(mb) - 1]) + mb = db.root.messages[username] + if opts.search: + messages = [msg for msg in mb if (opts.search in msg.text.lower()) or (opts.search in msg.msg_to[0].lower()) + or (opts.search in msg.msg_from.lower())] else: - for i in range(0,len(mb)): - messages.append(mb[i]) - for msg in messages: - # do other filtering. - - - + messages = [msg for msg in mb] -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") if not user_authorized(conn, db): 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") if req.method is Request.Method.GET: handle_message_get(req, conn, db) + if req.method is Request.Method.POST: + handle_message_post(req, conn, db) else: send_blank_response(conn, req, status_code=404) diff --git a/src/packetserver/server/requests.py b/src/packetserver/server/requests.py index 2a30223..3432be1 100644 --- a/src/packetserver/server/requests.py +++ b/src/packetserver/server/requests.py @@ -5,6 +5,7 @@ from packetserver.common import Message, Request, Response, PacketServerConnecti from .bulletin import bulletin_root_handler from .users import user_root_handler, user_authorized from .objects import object_root_handler +from .messages import message_root_handler import logging from typing import Union import ZODB @@ -50,7 +51,8 @@ standard_handlers = { "": root_root_handler, "bulletin": bulletin_root_handler, "user": user_root_handler, - "object": object_root_handler + "object": object_root_handler, + "message": message_root_handler }