From 67da5be3e90df43651218cac7ef833e684a198f0 Mon Sep 17 00:00:00 2001
From: hwipl <33433250+hwipl@users.noreply.github.com>
Date: Wed, 18 Dec 2019 15:47:50 +0100
Subject: [PATCH] split up slixmppd.py
Split up the module slixmppd.py into the modules client.py, main.py,
and server.py.
Signed-off-by: hwipl <33433250+hwipl@users.noreply.github.com>
---
nuqql_slixmppd/{slixmppd.py => client.py} | 298 +---------------------
nuqql_slixmppd/main.py | 21 ++
nuqql_slixmppd/server.py | 298 ++++++++++++++++++++++
setup.py | 2 +-
slixmppd.py | 4 +-
5 files changed, 325 insertions(+), 298 deletions(-)
rename nuqql_slixmppd/{slixmppd.py => client.py} (59%)
mode change 100755 => 100644
create mode 100755 nuqql_slixmppd/main.py
create mode 100644 nuqql_slixmppd/server.py
diff --git a/nuqql_slixmppd/slixmppd.py b/nuqql_slixmppd/client.py
old mode 100755
new mode 100644
similarity index 59%
rename from nuqql_slixmppd/slixmppd.py
rename to nuqql_slixmppd/client.py
index c2da1bf..6e8efbb
--- a/nuqql_slixmppd/slixmppd.py
+++ b/nuqql_slixmppd/client.py
@@ -1,33 +1,24 @@
-#!/usr/bin/env python3
-
"""
-slixmppd
+slixmppd backend client
"""
import time
-import asyncio
-import html
-import re
import unicodedata
-import logging
-import stat
-import os
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
-from threading import Thread, Lock, Event
+from threading import Lock
# slixmpp
from slixmpp import ClientXMPP # type: ignore
# from slixmpp.exceptions import IqError, IqTimeout
# nuqql-based
-from nuqql_based.based import Based
from nuqql_based.callback import Callback
from nuqql_based.message import Message
if TYPE_CHECKING: # imports for typing
+ # TODO: move Lock here?
from nuqql_based.account import Account
- from nuqql_based.config import Config
# slixmppd version
VERSION = "0.4"
@@ -391,286 +382,3 @@ def _chat_invite(self, chat: str, user: str) -> None:
"""
self.plugin['xep_0045'].invite(chat, user)
-
-
-class BackendServer:
- """
- Backend server class, manages the BackendClients for connections to
- IM networks
- """
-
- def __init__(self) -> None:
- self.connections: Dict[int, BackendClient] = {}
- self.threads: Dict[int, Tuple[Thread, Event]] = {}
-
- # register callbacks
- callbacks = [
- # based events
- (Callback.BASED_CONFIG, self._based_config),
- (Callback.BASED_INTERRUPT, self._based_interrupt),
- (Callback.BASED_QUIT, self._based_quit),
-
- # nuqql messages
- (Callback.QUIT, self.stop_thread),
- (Callback.ADD_ACCOUNT, self.add_account),
- (Callback.DEL_ACCOUNT, self.del_account),
- (Callback.SEND_MESSAGE, self.send_message),
- (Callback.SET_STATUS, self.enqueue),
- (Callback.GET_STATUS, self.enqueue),
- (Callback.CHAT_LIST, self.enqueue),
- (Callback.CHAT_JOIN, self.enqueue),
- (Callback.CHAT_PART, self.enqueue),
- (Callback.CHAT_SEND, self.chat_send),
- (Callback.CHAT_USERS, self.enqueue),
- (Callback.CHAT_INVITE, self.enqueue),
- ]
-
- # start based
- self.based = Based("slixmppd", VERSION, callbacks)
-
- def start(self) -> None:
- """
- Start server
- """
-
- self.based.start()
-
- def enqueue(self, account_id: int, cmd: Callback, params: Tuple) -> str:
- """
- Helper for adding commands to the command queue of the account/client
- """
-
- try:
- xmpp = self.connections[account_id]
- except KeyError:
- # no active connection
- return ""
-
- xmpp.enqueue_command(cmd, params)
-
- return ""
-
- def send_message(self, account_id: int, cmd: Callback,
- params: Tuple) -> str:
- """
- send a message to a jabber id on an account
- """
-
- # parse parameters
- if len(params) > 2:
- dest, msg, msg_type = params
- else:
- dest, msg = params
- msg_type = "chat"
-
- # nuqql sends a html-escaped message; construct "plain-text" version
- # and xhtml version using nuqql's message and use them as message body
- # later
- html_msg = \
- '
{}'.format(msg)
- msg = html.unescape(msg)
- msg = "\n".join(re.split("
", msg, flags=re.IGNORECASE))
-
- # send message
- self.enqueue(account_id, cmd, (dest, msg, html_msg, msg_type))
-
- return ""
-
- def chat_send(self, account_id: int, _cmd: Callback, params: Tuple) -> str:
- """
- Send message to chat on account
- """
-
- chat, msg = params
- return self.send_message(account_id, Callback.SEND_MESSAGE,
- (chat, msg, "groupchat"))
-
- @staticmethod
- def _reconnect(xmpp, last_connect: float) -> float:
- """
- Try to reconnect to the server if last connect is older than 10
- seconds.
- """
-
- cur_time = time.time()
- if cur_time - last_connect < 10:
- return last_connect
-
- print("Reconnecting:", xmpp.account.user)
- xmpp.connect()
- return cur_time
-
- def run_client(self, account: "Account", ready: Event,
- running: Event) -> None:
- """
- Run client connection in a new thread,
- as long as running Event is set to true.
- """
-
- # get event loop for thread
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
-
- # create a new lock for the thread
- lock = Lock()
-
- # start client connection
- xmpp = BackendClient(account, lock)
- xmpp.register_plugin('xep_0071') # XHTML-IM
- xmpp.register_plugin('xep_0082') # XMPP Date and Time Profiles
- xmpp.register_plugin('xep_0203') # Delayed Delivery, time stamps
- xmpp.register_plugin('xep_0030') # Service Discovery
- xmpp.register_plugin('xep_0045') # Multi-User Chat
- xmpp.register_plugin('xep_0199') # XMPP Ping
- xmpp.connect()
- last_connect = time.time()
-
- # save client connection in active connections dictionary
- self.connections[account.aid] = xmpp
-
- # thread is ready to enter main loop, inform caller
- ready.set()
-
- # enter main loop, and keep running until "running" is set to false
- # by the KeyboardInterrupt
- while running.is_set():
- # process xmpp client for 0.1 seconds, then send pending outgoing
- # messages and update the (safe copy of the) buddy list
- xmpp.process(timeout=0.1)
- # if account is offline, skip other steps to avoid issues with
- # sending commands/messages over the (uninitialized) xmpp
- # connection
- if xmpp.account.status == "offline":
- last_connect = self._reconnect(xmpp, last_connect)
- continue
- xmpp.handle_queue()
- xmpp.update_buddies()
-
- def add_account(self, account_id: int, _cmd: Callback,
- params: Tuple) -> str:
- """
- Add a new account (from based) and run a new slixmpp client thread for
- it
- """
-
- # only handle xmpp accounts
- account = params[0]
- if account.type != "xmpp":
- return ""
-
- # make sure other loggers do not also write to root logger
- account.logger.propagate = False
-
- # event to signal thread is ready
- ready = Event()
-
- # event to signal if thread should stop
- running = Event()
- running.set()
-
- # create and start thread
- new_thread = Thread(target=self.run_client, args=(account, ready,
- running))
- new_thread.start()
-
- # save thread in active threads dictionary
- self.threads[account_id] = (new_thread, running)
-
- # wait until thread initialized everything
- ready.wait()
-
- return ""
-
- def del_account(self, account_id: int, _cmd: Callback,
- _params: Tuple) -> str:
- """
- Delete an existing account (in based) and
- stop slixmpp client thread for it
- """
-
- # stop thread
- thread, running = self.threads[account_id]
- running.clear()
- thread.join()
-
- # cleanup
- del self.connections[account_id]
- del self.threads[account_id]
-
- return ""
-
- @staticmethod
- def init_logging(config: "Config") -> None:
- """
- Configure logging module, so slixmpp logs are written to a file
- """
-
- # determine logging path from command line parameters and
- # make sure it exists
- logs_dir = config.get_dir() / "logs"
- logs_dir.mkdir(parents=True, exist_ok=True)
- log_file = logs_dir / "slixmpp.log"
-
- # configure logging module to write to file
- log_format = "%(asctime)s %(levelname)-5.5s [%(name)s] %(message)s"
- loglevel = config.get_loglevel()
- logging.basicConfig(filename=log_file, level=loglevel,
- format=log_format, datefmt="%s")
- os.chmod(log_file, stat.S_IRWXU)
-
- def stop_thread(self, account_id: int, _cmd: Callback,
- _params: Tuple) -> str:
- """
- Quit backend/stop client thread
- """
-
- # stop thread
- print("Signalling account thread to stop.")
- _thread, running = self.threads[account_id]
- running.clear()
- return ""
-
- def _based_config(self, _account_id: int, _cmd: Callback,
- params: Tuple) -> str:
- """
- Config event in based
- """
-
- config = params[0]
- self.init_logging(config)
- return ""
-
- def _based_interrupt(self, _account_id: int, _cmd: Callback,
- _params: Tuple) -> str:
- """
- KeyboardInterrupt event in based
- """
-
- for _thread, running in self.threads.values():
- print("Signalling account thread to stop.")
- running.clear()
- return ""
-
- def _based_quit(self, _account_id: int, _cmd: Callback,
- _params: Tuple) -> str:
- """
- Based shut down event
- """
-
- print("Waiting for all threads to finish. This might take a while.")
- for thread, _running in self.threads.values():
- thread.join()
- return ""
-
-
-def main() -> None:
- """
- Main function, initialize everything and start server
- """
-
- server = BackendServer()
- server.start()
-
-
-if __name__ == '__main__':
- main()
diff --git a/nuqql_slixmppd/main.py b/nuqql_slixmppd/main.py
new file mode 100755
index 0000000..cac11ea
--- /dev/null
+++ b/nuqql_slixmppd/main.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+"""
+slixmppd main entry point
+"""
+
+# slixmppd
+from nuqql_slixmppd.server import BackendServer
+
+
+def main() -> None:
+ """
+ Main function, initialize everything and start server
+ """
+
+ server = BackendServer()
+ server.start()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/nuqql_slixmppd/server.py b/nuqql_slixmppd/server.py
new file mode 100644
index 0000000..1a36c2e
--- /dev/null
+++ b/nuqql_slixmppd/server.py
@@ -0,0 +1,298 @@
+"""
+slixmppd backend server
+"""
+
+import time
+import asyncio
+import html
+import re
+import logging
+import stat
+import os
+
+from typing import TYPE_CHECKING, Dict, Tuple
+from threading import Thread, Lock, Event
+
+# slixmppd
+from nuqql_slixmppd.client import BackendClient
+
+# nuqql-based
+from nuqql_based.based import Based
+from nuqql_based.callback import Callback
+
+if TYPE_CHECKING: # imports for typing
+ from nuqql_based.account import Account
+ from nuqql_based.config import Config
+
+# slixmppd version
+VERSION = "0.4"
+
+
+class BackendServer:
+ """
+ Backend server class, manages the BackendClients for connections to
+ IM networks
+ """
+
+ def __init__(self) -> None:
+ self.connections: Dict[int, BackendClient] = {}
+ self.threads: Dict[int, Tuple[Thread, Event]] = {}
+
+ # register callbacks
+ callbacks = [
+ # based events
+ (Callback.BASED_CONFIG, self._based_config),
+ (Callback.BASED_INTERRUPT, self._based_interrupt),
+ (Callback.BASED_QUIT, self._based_quit),
+
+ # nuqql messages
+ (Callback.QUIT, self.stop_thread),
+ (Callback.ADD_ACCOUNT, self.add_account),
+ (Callback.DEL_ACCOUNT, self.del_account),
+ (Callback.SEND_MESSAGE, self.send_message),
+ (Callback.SET_STATUS, self.enqueue),
+ (Callback.GET_STATUS, self.enqueue),
+ (Callback.CHAT_LIST, self.enqueue),
+ (Callback.CHAT_JOIN, self.enqueue),
+ (Callback.CHAT_PART, self.enqueue),
+ (Callback.CHAT_SEND, self.chat_send),
+ (Callback.CHAT_USERS, self.enqueue),
+ (Callback.CHAT_INVITE, self.enqueue),
+ ]
+
+ # start based
+ self.based = Based("slixmppd", VERSION, callbacks)
+
+ def start(self) -> None:
+ """
+ Start server
+ """
+
+ self.based.start()
+
+ def enqueue(self, account_id: int, cmd: Callback, params: Tuple) -> str:
+ """
+ Helper for adding commands to the command queue of the account/client
+ """
+
+ try:
+ xmpp = self.connections[account_id]
+ except KeyError:
+ # no active connection
+ return ""
+
+ xmpp.enqueue_command(cmd, params)
+
+ return ""
+
+ def send_message(self, account_id: int, cmd: Callback,
+ params: Tuple) -> str:
+ """
+ send a message to a jabber id on an account
+ """
+
+ # parse parameters
+ if len(params) > 2:
+ dest, msg, msg_type = params
+ else:
+ dest, msg = params
+ msg_type = "chat"
+
+ # nuqql sends a html-escaped message; construct "plain-text" version
+ # and xhtml version using nuqql's message and use them as message body
+ # later
+ html_msg = \
+ '{}'.format(msg)
+ msg = html.unescape(msg)
+ msg = "\n".join(re.split("
", msg, flags=re.IGNORECASE))
+
+ # send message
+ self.enqueue(account_id, cmd, (dest, msg, html_msg, msg_type))
+
+ return ""
+
+ def chat_send(self, account_id: int, _cmd: Callback, params: Tuple) -> str:
+ """
+ Send message to chat on account
+ """
+
+ chat, msg = params
+ return self.send_message(account_id, Callback.SEND_MESSAGE,
+ (chat, msg, "groupchat"))
+
+ @staticmethod
+ def _reconnect(xmpp, last_connect: float) -> float:
+ """
+ Try to reconnect to the server if last connect is older than 10
+ seconds.
+ """
+
+ cur_time = time.time()
+ if cur_time - last_connect < 10:
+ return last_connect
+
+ print("Reconnecting:", xmpp.account.user)
+ xmpp.connect()
+ return cur_time
+
+ def run_client(self, account: "Account", ready: Event,
+ running: Event) -> None:
+ """
+ Run client connection in a new thread,
+ as long as running Event is set to true.
+ """
+
+ # get event loop for thread
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ # create a new lock for the thread
+ lock = Lock()
+
+ # start client connection
+ xmpp = BackendClient(account, lock)
+ xmpp.register_plugin('xep_0071') # XHTML-IM
+ xmpp.register_plugin('xep_0082') # XMPP Date and Time Profiles
+ xmpp.register_plugin('xep_0203') # Delayed Delivery, time stamps
+ xmpp.register_plugin('xep_0030') # Service Discovery
+ xmpp.register_plugin('xep_0045') # Multi-User Chat
+ xmpp.register_plugin('xep_0199') # XMPP Ping
+ xmpp.connect()
+ last_connect = time.time()
+
+ # save client connection in active connections dictionary
+ self.connections[account.aid] = xmpp
+
+ # thread is ready to enter main loop, inform caller
+ ready.set()
+
+ # enter main loop, and keep running until "running" is set to false
+ # by the KeyboardInterrupt
+ while running.is_set():
+ # process xmpp client for 0.1 seconds, then send pending outgoing
+ # messages and update the (safe copy of the) buddy list
+ xmpp.process(timeout=0.1)
+ # if account is offline, skip other steps to avoid issues with
+ # sending commands/messages over the (uninitialized) xmpp
+ # connection
+ if xmpp.account.status == "offline":
+ last_connect = self._reconnect(xmpp, last_connect)
+ continue
+ xmpp.handle_queue()
+ xmpp.update_buddies()
+
+ def add_account(self, account_id: int, _cmd: Callback,
+ params: Tuple) -> str:
+ """
+ Add a new account (from based) and run a new slixmpp client thread for
+ it
+ """
+
+ # only handle xmpp accounts
+ account = params[0]
+ if account.type != "xmpp":
+ return ""
+
+ # make sure other loggers do not also write to root logger
+ account.logger.propagate = False
+
+ # event to signal thread is ready
+ ready = Event()
+
+ # event to signal if thread should stop
+ running = Event()
+ running.set()
+
+ # create and start thread
+ new_thread = Thread(target=self.run_client, args=(account, ready,
+ running))
+ new_thread.start()
+
+ # save thread in active threads dictionary
+ self.threads[account_id] = (new_thread, running)
+
+ # wait until thread initialized everything
+ ready.wait()
+
+ return ""
+
+ def del_account(self, account_id: int, _cmd: Callback,
+ _params: Tuple) -> str:
+ """
+ Delete an existing account (in based) and
+ stop slixmpp client thread for it
+ """
+
+ # stop thread
+ thread, running = self.threads[account_id]
+ running.clear()
+ thread.join()
+
+ # cleanup
+ del self.connections[account_id]
+ del self.threads[account_id]
+
+ return ""
+
+ @staticmethod
+ def init_logging(config: "Config") -> None:
+ """
+ Configure logging module, so slixmpp logs are written to a file
+ """
+
+ # determine logging path from command line parameters and
+ # make sure it exists
+ logs_dir = config.get_dir() / "logs"
+ logs_dir.mkdir(parents=True, exist_ok=True)
+ log_file = logs_dir / "slixmpp.log"
+
+ # configure logging module to write to file
+ log_format = "%(asctime)s %(levelname)-5.5s [%(name)s] %(message)s"
+ loglevel = config.get_loglevel()
+ logging.basicConfig(filename=log_file, level=loglevel,
+ format=log_format, datefmt="%s")
+ os.chmod(log_file, stat.S_IRWXU)
+
+ def stop_thread(self, account_id: int, _cmd: Callback,
+ _params: Tuple) -> str:
+ """
+ Quit backend/stop client thread
+ """
+
+ # stop thread
+ print("Signalling account thread to stop.")
+ _thread, running = self.threads[account_id]
+ running.clear()
+ return ""
+
+ def _based_config(self, _account_id: int, _cmd: Callback,
+ params: Tuple) -> str:
+ """
+ Config event in based
+ """
+
+ config = params[0]
+ self.init_logging(config)
+ return ""
+
+ def _based_interrupt(self, _account_id: int, _cmd: Callback,
+ _params: Tuple) -> str:
+ """
+ KeyboardInterrupt event in based
+ """
+
+ for _thread, running in self.threads.values():
+ print("Signalling account thread to stop.")
+ running.clear()
+ return ""
+
+ def _based_quit(self, _account_id: int, _cmd: Callback,
+ _params: Tuple) -> str:
+ """
+ Based shut down event
+ """
+
+ print("Waiting for all threads to finish. This might take a while.")
+ for thread, _running in self.threads.values():
+ thread.join()
+ return ""
diff --git a/setup.py b/setup.py
index c94cd40..362b42b 100755
--- a/setup.py
+++ b/setup.py
@@ -27,7 +27,7 @@
url="https://github.com/hwipl/nuqql-slixmppd",
packages=["nuqql_slixmppd"],
entry_points={
- "console_scripts": ["nuqql-slixmppd = nuqql_slixmppd.slixmppd:main"]
+ "console_scripts": ["nuqql-slixmppd = nuqql_slixmppd.main:main"]
},
classifiers=CLASSIFIERS,
python_requires='>=3.6',
diff --git a/slixmppd.py b/slixmppd.py
index d4c3e65..fe01545 100755
--- a/slixmppd.py
+++ b/slixmppd.py
@@ -6,8 +6,8 @@
import sys
-import nuqql_slixmppd.slixmppd
+import nuqql_slixmppd.main
import nuqql_slixmppd
# start slixmppd
-sys.exit(nuqql_slixmppd.slixmppd.main())
+sys.exit(nuqql_slixmppd.main.main())