From f40536802b1990d6aff5c453a5f61c238262f247 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Tue, 12 May 2020 20:03:14 -0500 Subject: [PATCH] Added basic chat example. --- examples/chat app/client/client.py | 26 +++++++++ examples/chat app/client/gui.py | 90 +++++++++++++++++++++++++++++ examples/chat app/client/main.py | 91 ++++++++++++++++++++++++++++++ examples/chat app/client/output.py | 31 ++++++++++ examples/chat app/requirements.txt | 7 +++ examples/chat app/server/server.py | 51 +++++++++++++++++ 6 files changed, 296 insertions(+) create mode 100644 examples/chat app/client/client.py create mode 100644 examples/chat app/client/gui.py create mode 100644 examples/chat app/client/main.py create mode 100644 examples/chat app/client/output.py create mode 100644 examples/chat app/requirements.txt create mode 100644 examples/chat app/server/server.py diff --git a/examples/chat app/client/client.py b/examples/chat app/client/client.py new file mode 100644 index 0000000..e79efce --- /dev/null +++ b/examples/chat app/client/client.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +""" client communication for this simple chat example application. +This is a class derived from enetcomponents.client.client, which sends all data over pubsub via the topic response. +""" +from enetcomponents import client +from pubsub import pub + +class client(client.client): + + def network(self, event, data): + """ This functions receives data from an enet server in the following protocol: + dict(action="some_command", **kwargs) + This function will send all data to whatever listener in the pubsub stack by using the topic "response". + """ + f = data.get("action") + if f == None: + print("Error: Invalid data in protocol. %r" % (data)) + return + pub.sendMessage("response", data=data) + + def connected(self, peer): + pub.sendMessage("ask_login") + + def disconnected(self, peer): + pub.sendMessage("disconnected") + diff --git a/examples/chat app/client/gui.py b/examples/chat app/client/gui.py new file mode 100644 index 0000000..876cea8 --- /dev/null +++ b/examples/chat app/client/gui.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +""" Dfinition of all GUI components. """ +import wx + +class loginDialog(wx.Dialog): + + def __init__(self, title="Login"): + super(loginDialog, self).__init__(parent=None, id=wx.ID_ANY) + self.SetTitle(title) + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + label1 = wx.StaticText(panel, wx.ID_ANY, "Username: ") + self.username = wx.TextCtrl(panel, wx.ID_ANY, size=(380, -1)) + s = wx.BoxSizer(wx.HORIZONTAL) + s.Add(label1, 0, wx.ALL, 5) + s.Add(self.username, 0, wx.ALL, 5) + sizer.Add(s, 0, wx.ALL, 5) + label1 = wx.StaticText(panel, wx.ID_ANY, "Server: ") + self.server = wx.TextCtrl(panel, wx.ID_ANY, "localhost", size=(380, -1)) + s = wx.BoxSizer(wx.HORIZONTAL) + s.Add(label1, 0, wx.ALL, 5) + s.Add(self.server, 0, wx.ALL, 5) + sizer.Add(s, 0, wx.ALL, 5) + label1 = wx.StaticText(panel, wx.ID_ANY, "Port: ") + self.port = wx.SpinCtrl(panel, wx.ID_ANY, size=(380, -1)) + self.port.SetRange(1024, 65000) + self.port.SetValue(33333) + s = wx.BoxSizer(wx.HORIZONTAL) + s.Add(label1, 0, wx.ALL, 5) + s.Add(self.port, 0, wx.ALL, 5) + sizer.Add(s, 0, wx.ALL, 5) + +# label2 = wx.StaticText(panel, wx.ID_ANY, "Password: ") +# self.password = wx.TextCtrl(panel, wx.ID_ANY, size=(380, -1), style=wx.TE_PASSWORD) +# ss = wx.BoxSizer(wx.HORIZONTAL) +# ss.Add(label2, 0, wx.ALL, 5) +# ss.Add(self.password, 0, wx.ALL, 5) +# sizer.Add(ss, 0, wx.ALL, 5) + ok = wx.Button(panel, wx.ID_OK, "Log in") + ok.SetDefault() + cancel = wx.Button(panel, wx.ID_CANCEL) + self.SetEscapeId(wx.ID_CANCEL) + bs = wx.BoxSizer(wx.HORIZONTAL) + bs.Add(ok, 0, wx.ALL, 5) + bs.Add(cancel, 0, wx.ALL, 5) + sizer.Add(bs, 0, wx.ALL, 5) + panel.SetSizer(sizer) + self.SetClientSize(sizer.CalcMin()) + +class appFrame(wx.Frame): + def __init__(self): + super(appFrame, self).__init__(parent=None, title="Chat Window") + self.Maximize(True) + self.panel = wx.Panel(self) + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.sb = self.CreateStatusBar() + lbl = wx.StaticText(self.panel, wx.ID_ANY, "menu") + self.list = wx.ListBox(self.panel, wx.ID_ANY) + self.sizer.Add(lbl, 0, wx.GROW) + self.sizer.Add(self.list, 1, wx.GROW) + lbl = wx.StaticText(self.panel, -1, "Chat") + self.chat = wx.TextCtrl(self.panel, -1) + sizerchat = wx.BoxSizer(wx.HORIZONTAL) + sizerchat.Add(lbl, 0, wx.ALL, 5) + sizerchat.Add(self.chat, 0, wx.ALL, 5) + self.sizer.Add(sizerchat, 0, wx.ALL, 5) + lbl1 = wx.StaticText(self.panel, wx.ID_ANY, "History") + self.history = wx.TextCtrl(self.panel, wx.ID_ANY, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(500, 300)) + box = wx.BoxSizer(wx.HORIZONTAL) + box.Add(lbl1, 0, wx.ALL, 5) + box.Add(self.history, 0, wx.ALL, 5) + self.sizer.Add(box, 0, wx.ALL, 5) + self.panel.SetSizerAndFit(self.sizer) + + def get_item(self): + return self.list.GetSelection() + + def add_message(self, message, reverse=False): + old_line = self.history.GetNumberOfLines() + point = self.history.GetInsertionPoint() + if reverse: + self.history.SetValue(message+"\n"+self.history.GetValue()) + else: + self.history.AppendText(message+"\n") + self.history.SetInsertionPoint(point) + new_line = self.history.GetNumberOfLines()#.count("\n") + return (old_line, new_line) + + def show_connection_error(self): + msg = wx.MessageDialog(None, "Connection error. Try again", "error", style=wx.ICON_ERROR).ShowModal() \ No newline at end of file diff --git a/examples/chat app/client/main.py b/examples/chat app/client/main.py new file mode 100644 index 0000000..d9f0b09 --- /dev/null +++ b/examples/chat app/client/main.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +import sys +import threading +import wx +import client +import output +import gui +from pubsub import pub + +# Client instance. +c = None + +# thread to keep the client running. +t = None + +class controller(object): + def __init__(self, window): + super(controller, self).__init__() + self.window = window + self.connect_events() + self.window.Show() + + def connect_events(self): + self.window.chat.Bind(wx.EVT_CHAR_HOOK, self.on_process) + pub.subscribe(self.response, "response") + pub.subscribe(self.ask_login, "ask_login") + pub.subscribe(self.disconnected, "disconnected") + + def on_process(self, event): + key = event.GetKeyCode() + if key == wx.WXK_RETURN: + self.send_message() + event.Skip() + + def send_message(self): + global c + message = self.window.chat.GetValue() + if message == "" or message == None: + return wx.Bell() + # Otherwise, message does exist. + data = dict(action="send_message", message=message) + c.send_data(0, data) + self.window.chat.ChangeValue("") + + def response(self, data): + command = data.get("action") + if hasattr(self, "cmd_"+command): + getattr(self, "cmd_"+command)(data) + + def cmd_connected(self, data): + connected = data.get("nickname") + msg = "{} has entered this platform".format(connected) + self.window.add_message(msg) + + def cmd_message(self, data): + msg = data.get("message") + nickname = data.get("nickname") + msg = "{0}: {1}".format(nickname, msg) + output.speak(msg) + self.window.add_message(msg) + + def ask_login(self): + global c + data = dict(action="login", nickname=self.username) + c.send_data(0, data) + + def disconnected(self): + self.window.show_connection_error() + wx.GetApp().ExitMainLoop() + +def setup(): + global c, t + output.setup() + app = wx.App() + d = gui.loginDialog() + f = gui.appFrame() + mainController = controller(f) + if d.ShowModal() != wx.ID_OK: + return + username = d.username.GetValue() + server = bytes(d.server.GetValue(), "utf-8") + port = int(d.port.GetValue()) + mainController.username = username + d.Destroy() + c = client.client(host=server, port=port) + t = threading.Thread(target=c.run) + t.start() + app.MainLoop() + c.close() + +setup() \ No newline at end of file diff --git a/examples/chat app/client/output.py b/examples/chat app/client/output.py new file mode 100644 index 0000000..43c9194 --- /dev/null +++ b/examples/chat app/client/output.py @@ -0,0 +1,31 @@ +# *- coding: utf-8 -*- +""" Simple (really simple) module to provide text to speech output in a chat window.""" +import logging as original_logging +logger = original_logging.getLogger('core.output') +from accessible_output2 import outputs +import sys + +speaker = None +retries = 0 + +def speak(text, interrupt=0): + global speaker, retries + if not speaker: + setup() + try: + speaker.speak(text, interrupt) + except: + if retries < 5: + retries = retries + 1 + speak(text) + +def setup (): + global speaker + logger.debug("Initializing output subsystem.") + try: + speaker = outputs.auto.Auto() + except: + logger.exception("Output: Error during initialization.") + +def enable_sapi(): + speaker = outputs.sapi.SAPI5() \ No newline at end of file diff --git a/examples/chat app/requirements.txt b/examples/chat app/requirements.txt new file mode 100644 index 0000000..a1c770f --- /dev/null +++ b/examples/chat app/requirements.txt @@ -0,0 +1,7 @@ +pyenet +wxpython==4.0.3 +pypubsub +pywin32 +git+https://code.manuelcortez.net/manuelcortez/libloader +git+https://code.manuelcortez.net/manuelcortez/platform_utils +git+https://code.manuelcortez.net/manuelcortez/accessible_output2 \ No newline at end of file diff --git a/examples/chat app/server/server.py b/examples/chat app/server/server.py new file mode 100644 index 0000000..66c91e3 --- /dev/null +++ b/examples/chat app/server/server.py @@ -0,0 +1,51 @@ +from enetcomponents import server + +class channel(server.channel): + + def __init__(self, *args, **kwargs): + super(channel, self).__init__(*args, **kwargs) + self.nickname = None + + def network(self, event, data): + f = data.get("action") + if f == None: + print("Error: Invalid data in protocol. %r" % (data)) + return + if hasattr(self, "cmd_"+f) == False: + print("Error: function cmd_{} does not exist".format(f)) + return + getattr(self, "cmd_"+f)(data) + + def cmd_login(self, data): + nickname = data.get("nickname") + self.nickname = nickname + self.room = "public" + d = dict(action="connected", nickname=nickname) + self.server.send_to_all(0, d) + + def cmd_send_message(self, data): + data.update(nickname=self.nickname, action="message") + for channel in self.server.peers: + if channel.room == self.room: + channel.send_data(0, data) + +class server(server.server): + + def __init__(self, *args, **kwargs): + super(server, self).__init__(*args, **kwargs) + self.rooms = list() + + def connected(self, peer): + p = channel(self, peer) + self.peers[p] = True + + def disconnected(self, peer): + for channel in self.peers: + if peer.incomingPeerID == channel.peer.incomingPeerID: + del self.peers[channel] + break + +if __name__ == "__main__": + print("Starting chat server...") + s = server() + s.run() \ No newline at end of file