#!/usr/bin/env python # coding: utf-8 """ Logic classes for Chatwisted clients. """ from twisted.protocols.basic import LineReceiver from twisted.internet.protocol import ClientFactory from twisted.internet import reactor from twisted.internet.error import ReactorNotRunning, CannotListenError from twisted.python.failure import Failure from chatwisted.plugins import PLUGINS from chatwisted.parser import CommandParser from chatwisted.server import ChatServerFactory class PluginClient(object): """ Metaclass for a plugin's client side logic and parent for all GUI-child-classes. Defines all command methods. """ name = "chat" gui = "META" def __init__(self, factory, name): """ :Parameters: factory : ServerFactory server factory object to register at name : string name of this group (should be unique) """ self.factory = factory self.group_name = name self.factory.groups[self.group_name] = self self.factory.parser.get_cmds_from_obj(self, cmd_prefix=name+":") self.logged_lines = [] self.send_cmd("join", "") def send_cmd(self, cmd, arg_string): """ Send a command to the server """ self.factory.send_cmd("%s:%s" % (self.group_name, cmd), arg_string) def send_message(self, line): """ To call with a line the user wants to send :param line: the line to send :type line: string :rtype: boolean :return: whether the line could be sent or not """ if not line.startswith("/"): line = "/say %s" % line line = line.replace("/", "/%s:" % self.group_name) return self.factory.sendLine(line) def log(self, text): """ logs text by appending it as a new line to the log. (to overwrite!) """ self.logged_lines.append(text) def cmd_leave(self): """ leave the group now! """ self.on_quit() def cmd_join_failed(self): """ could not join the group, exiting """ self.on_quit() def cmd_say(self, speaker, words): """ so said sth """ self.log("%s: %s" % (speaker, words)) def cmd_me(self, actor, words): """ so did sth """ self.log("%s %s" % (actor, words)) def cmd_write(self, words): """ Write text directly into the chat. """ self.log(words) def cmd_list(self, clients): """ Updates the user list. """ self.log("In the group '%s' are now: %s" % (self.group_name, clients)) def cmd_error(self, err): """ Recieves errors from the server """ self.log("ERROR: %s" % err) def on_quit(self): """ logout and quit plugin """ self.send_cmd("leave", "") self.factory.leave_group(self) class ChatClientProtocol(LineReceiver): """ Simple Echo Client Protocol registers at self.factory and echos all incoming data to factory to log (display in the textbox) """ factory = None def connectionMade(self): """ Called when connection is made and self.factory is set registers self at factory as the protocolobject """ self.factory.protocolobj = self self.factory.send_buffer() def lineReceived(self, line): """ Called when one line (i.e. all data until self.delimiter) was received simply echo it to self.factory """ self.factory.lineReceived(line) def connectionLost(self, dummy=Failure): """ Called when the connection was lost stop the reactor and with it the mainloop and quit the program :param dummy: unused reason, defined by LineReceiver """ try: reactor.stop() except ReactorNotRunning: pass class ChatClientFactory(ClientFactory): """ Client Factory holds the main program (incl, GUI) starts a server or connects to a client and builds the local Protocol object for that connection. """ protocol = ChatClientProtocol protocolobj = None gui = None groups = {} def __init__(self): """ Create a CommandParser and parse this class for command methods. """ self.parser = CommandParser() self.parser.get_cmds_from_obj(self) self.buffer = [] self.waiting_to_enter = [] def clientConnectionFailed(self, connector, reason): """ Could not connect to a server """ self.stop_reactor() def clientConnectionLost(self, connector, reason): """ Lost connection to a server """ self.stop_reactor() def sendLine(self, line, buff=False): """ Send a line to the server wrapper function to call protocolobj.sendLine safely :param line: the line to send to the server :type line: string :return: whether `line` was sent or not :rtype: boolean """ if self.protocolobj and line: self.protocolobj.sendLine(line.encode('utf8')) return True elif buff: self.buffer.append(line) return True else: return False def send_buffer(self): """ send buffered lines to the server """ for line in self.buffer: self.sendLine(line) def lineReceived(self, line): """ parse every incomming line for commands """ self.parser.parse(line) def send_cmd(self, cmd, arg_string): """ Sends the command `cmd` to the server with `arg_string` as parameters. :Parameters: cmd : string the command to send arg_string : string arguments as string. (The receiver has to encode them itself.) """ self.sendLine("/%s %s" % (cmd, arg_string)) def start_client(self, ip, port, name): """ Start a client and connect to a server :Parameters: ip : string ip to connect to port : int or None port to use """ try: reactor.connectTCP(ip, port, self) self.sendLine(name, True) return True except KeyboardInterrupt: raise except: #! except what? print "CLIENT NOT STARTED" return False def start_server(self, port, name): """ Start a server and connect to it as client :Parameters: port : int or None port to use """ try: reactor.listenTCP(port, ChatServerFactory()) self.start_client("localhost", port, name) return True except CannotListenError: self.gui.cmd_error("Port already in use") return False def stop_reactor(self): """ stop the twisted reactor if its runningmyfiles.php """ try: reactor.stop() except ReactorNotRunning: pass def enter_group(self, group): """ First step to enter a group. Asks the server for more information about group and puts the group into the waiting list. """ self.waiting_to_enter.append(group) self.send_cmd("%s:info" % group, "") def leave_group(self, group): """ Leave a group directly. Destroys the refference from the grouplist and unregisters all group commands at the parser. """ print self.groups del self.groups[group.group_name] print self.groups # dont remove commands for groups with a name starting with group.name self.parser.unregister(group.group_name + ":") self.send_cmd("group_list", "") def create_group(self, plugin_name, group_name): """ Create a new group of the given type on the server. """ self.send_cmd("create", "%s %s" % (plugin_name, group_name)) def cmd_group_created(self, plugin_name, group_name): """ Called by the server when a group creation was successfull. Enters the group. """ if plugin_name in PLUGINS: if self.gui.name in PLUGINS[plugin_name]: self.gui.add_group(PLUGINS[plugin_name][self.gui.name], group_name) def cmd_info(self, group, type_, can_join): """ Called by the server after a group information request. """ print "got info", group, type_, can_join if group in self.waiting_to_enter: err = "" del self.waiting_to_enter[self.waiting_to_enter.index(group)] if type_ in PLUGINS: if self.gui.name in PLUGINS[type_]: if int(can_join): group_class = PLUGINS[type_][self.gui.name] self.gui.add_group(group_class, group) else: err = "Can not join group %s (closed or full)" % group else: err = "No %s-Plugin for your Gui toolkit" % type_ else: err = "Plugin %s not installed" % type_ if err: self.gui.cmd_error(err)