From c0f51fdd1d3f93b15f8606fbdf2df2ea841115fe Mon Sep 17 00:00:00 2001 From: Vincent Hanquez Date: Wed, 22 Oct 2003 18:45:13 +0000 Subject: [PATCH] Initial revision --- common/__init__.py | 4 + common/hub.py | 65 ++ common/jabber.py | 1646 ++++++++++++++++++++++++++++++++++++++++++ common/optparser.py | 75 ++ common/plugin.py | 37 + common/thread.py | 40 + common/xmlstream.py | 605 ++++++++++++++++ doc/BUGS | 1 + doc/FEATURE | 3 + doc/HISTORY | 2 + doc/gajimrc | 9 + plugins/__init__.py | 1 + plugins/gtkgui.glade | 404 +++++++++++ plugins/gtkgui.py | 244 +++++++ runCore.py | 30 + 15 files changed, 3166 insertions(+) create mode 100644 common/__init__.py create mode 100644 common/hub.py create mode 100644 common/jabber.py create mode 100644 common/optparser.py create mode 100644 common/plugin.py create mode 100644 common/thread.py create mode 100644 common/xmlstream.py create mode 100644 doc/BUGS create mode 100644 doc/FEATURE create mode 100644 doc/HISTORY create mode 100644 doc/gajimrc create mode 100644 plugins/__init__.py create mode 100644 plugins/gtkgui.glade create mode 100644 plugins/gtkgui.py create mode 100644 runCore.py diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 000000000..13882eebf --- /dev/null +++ b/common/__init__.py @@ -0,0 +1,4 @@ +import hub +import jabber +import plugin +import xmlstream diff --git a/common/hub.py b/common/hub.py new file mode 100644 index 000000000..843b6a442 --- /dev/null +++ b/common/hub.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +## common/hub.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - David Ferlier +## +## Copyright (C) 2003 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import Queue +import common.plugin +import common.thread + +""" Hub definitions """ + +class GajimHub: + def __init__(self): + self.queues = {} + """ {event1:[queue1, queue2]} """ + self.events = {'NOTIFY':[], 'MSG':[], 'ROSTER':[]} + self.queueIn = self.newQueue('in', 100) + # END __init__ + + def newQueue(self, name, size): + """ Creates a new queue """ + qu = Queue.Queue(size) + self.queues[name] = qu + return qu + # END newQueue + + def newPlugin(self, name): + """Creates a new Plugin """ + qu = self.newQueue(name, 100) + pl = common.plugin.GajimPlugin(name, qu, self.queueIn) + return pl + # END newPlugin + + def register(self, name, event): + """ Records a plugin from an event """ + qu = self.queues[name] + self.events[event].append(qu) + # END register + + def sendPlugin(self, event, data): + """ Sends an event to registered plugins + NOTIFY : ('NOTIFY', (user, status, message)) + MSG : ('MSG', (user, msg)) + ROSTER : ('ROSTER', {jid:{'Online':_, 'Status':_, 'Show':_} ,jid:{}})""" + + if self.events.has_key(event): + for i in self.events[event]: + i.put((event, data)) + # END sendPlugin +# END GajimHub diff --git a/common/jabber.py b/common/jabber.py new file mode 100644 index 000000000..515c23756 --- /dev/null +++ b/common/jabber.py @@ -0,0 +1,1646 @@ +## jabber.py +## +## Copyright (C) 2001 Matthew Allum +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published +## by the Free Software Foundation; either version 2, or (at your option) +## any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. +## + + +"""\ + +__intro__ + +jabber.py is a Python module for the jabber instant messaging protocol. +jabber.py deals with the xml parsing and socket code, leaving the programmer +to concentrate on developing quality jabber based applications with Python. + +The eventual aim is to produce a fully featured easy to use library for +creating both jabber clients and servers. + +jabber.py requires at least python 2.0 and the XML expat parser module +( included in the standard Python distrubution ). + +It is developed on Linux but should run happily on over Unix's and win32. + +__Usage__ + +jabber.py basically subclasses the xmlstream classs and provides the +processing of jabber protocol elements into object instances as well +'helper' functions for parts of the protocol such as authentication +and roster management. + +An example of usage for a simple client would be ( only psuedo code !) + +<> Read documentation on jabber.org for the jabber protocol. + +<> Birth a jabber.Client object with your jabber servers host + +<> Define callback functions for the protocol elements you want to use + and optionally a disconnection. + +<> Authenticate with the server via auth method, or register via the + reg methods to get an account. + +<> Call sendInitPresence() and requestRoster() + +<> loop over process(). Send Iqs,messages and presences by birthing + them via there respective clients , manipulating them and using + the Client's send() method. + +<> Respond to incoming elements passed to your callback functions. + +<> Find bugs :) + + +""" + +# $Id: jabber.py,v 1.30 2003/02/20 10:22:33 shire Exp $ + +import xmlstream +import sha, time +from string import split,find,replace + +VERSION = 0.3 + +False = 0; +True = 1; + +USTR_ENCODING='iso-8859-1' + + +# +# JANA core namespaces +# from http://www.jabber.org/jana/namespaces.php as of 2003-01-12 +# "myname" means that namespace didnt have a name in the jabberd headers +# +NS_AGENT = "jabber:iq:agent" +NS_AGENTS = "jabber:iq:agents" +NS_AUTH = "jabber:iq:auth" +NS_CLIENT = "jabber:client" +NS_DELAY = "jabber:x:delay" +NS_OOB = "jabber:iq:oob" +NS_REGISTER = "jabber:iq:register" +NS_ROSTER = "jabber:iq:roster" +NS_XROSTER = "jabber:x:roster" # myname +NS_SERVER = "jabber:server" +NS_TIME = "jabber:iq:time" +NS_VERSION = "jabber:iq:version" + +NS_COMP_ACCEPT = "jabber:component:accept" # myname +NS_COMP_CONNECT = "jabber:component:connect" # myname + + + +# +# JANA JEP namespaces, ordered by JEP +# from http://www.jabber.org/jana/namespaces.php as of 2003-01-12 +# all names by jaclu +# +_NS_PROTOCOL = "http://jabber.org/protocol" # base for other +NS_PASS = "jabber:iq:pass" # JEP-0003 +NS_XDATA = "jabber:x:data" # JEP-0004 +NS_RPC = "jabber:iq:rpc" # JEP-0009 +NS_BROWSE = "jabber:iq:browse" # JEP-0011 +NS_LAST = "jabber:iq:last" #JEP-0012 +NS_PRIVACY = "jabber:iq:privacy" # JEP-0016 +NS_XEVENT = "jabber:x:event" # JEP-0022 +NS_XEXPIRE = "jabber:x:expire" # JEP-0023 +NS_XENCRYPTED = "jabber:x:encrypted" # JEP-0027 +NS_XSIGNED = "jabber:x:signed" # JEP-0027 +NS_P_MUC = _NS_PROTOCOL + "/muc" # JEP-0045 +NS_VCARD = "vcard-temp" # JEP-0054 + + +# +# Non JANA aproved, ordered by JEP +# all names by jaclu +# +_NS_P_DISCO = _NS_PROTOCOL + "/disco" # base for other +NS_P_DISC_INFO = _NS_P_DISCO + "#info" # JEP-0030 +NS_P_DISC_ITEMS = _NS_P_DISCO + "#items" # JEP-0030 +NS_P_COMMANDS = _NS_PROTOCOL + "/commands" # JEP-0050 + + +""" + 2002-01-11 jaclu + + Defined in jabberd/lib/lib.h, but not JANA aproved and not used in jabber.py + so commented out, should/could propably be removed... + + NS_ADMIN = "jabber:iq:admin" + NS_AUTH_OK = "jabber:iq:auth:0k" + NS_CONFERENCE = "jabber:iq:conference" + NS_ENVELOPE = "jabber:x:envelope" + NS_FILTER = "jabber:iq:filter" + NS_GATEWAY = "jabber:iq:gateway" + NS_OFFLINE = "jabber:x:offline" + NS_PRIVATE = "jabber:iq:private" + NS_SEARCH = "jabber:iq:search" + NS_XDBGINSERT = "jabber:xdb:ginsert" + NS_XDBNSLIST = "jabber:xdb:nslist" + NS_XHTML = "http://www.w3.org/1999/xhtml" + NS_XOOB = "jabber:x:oob" + NS_COMP_EXECUTE = "jabber:component:execute" # myname +""" + + +## Possible constants for Roster class .... hmmm ## +RS_SUB_BOTH = 0 +RS_SUB_FROM = 1 +RS_SUB_TO = 2 + +RS_ASK_SUBSCRIBE = 1 +RS_ASK_UNSUBSCRIBE = 0 + +RS_EXT_ONLINE = 2 +RS_EXT_OFFLINE = 1 +RS_EXT_PENDING = 0 + +############################################################################# + +def ustr(what, encoding=USTR_ENCODING): + """ + If sending object is already a unicode str, just + return it, otherwise convert it using encoding + """ + if type(what) == type(u''): + r = what + else: + r = what.__str__() + # make sure __str__() didnt return a unicode + if type(r) <> type(u''): + r = unicode(r,encoding,'replace') + return r + + +def str(what): + """quick and dirty catchall for all the str() usage. + + The code in this module should really be changed to call ustr() + instead of str() unless there is a good reason, + but remember all data on the wire are suposed to be unicode, + so this piece saves sloppy code ;) + + str() usage generally tend to break things for everybody that + doesnt speek english - and we are quite a few on this planet... + + If this is just to much to swallow, feel free to comment this out, + but please at least make sure that at least Client.send() uses ustr() + in that case + """ + return ustr(what) + + + +class Connection(xmlstream.Client): + """Forms the base for both Client and Component Classes""" + def __init__(self, host, port, namespace, + debug=False, log=False, connection=xmlstream.TCP): + + self.iq_hdlrs = [] + self.msg_hdlrs = [] + self.pres_hdlrs = [] + + self.disconnect_hdlr = None + self._expected = {} + + self._id = 0; + + self.lastErr = '' + self.lastErrCode = 0 + + xmlstream.Client.__init__(self, host, port, namespace, + debug=debug, log=log, + connection=connection ) + + + def connect(self): + """Attempts to connect to the specified jabber server. + Raises an IOError on failure""" + self.DEBUG("jabberpy connect called") + try: + xmlstream.Client.connect(self) + except xmlstream.error, e: + raise IOError(e) + + + def disconnect(self): + """Safely disconnects from the connected server""" + self.send(Presence(type='unavailable')); + xmlstream.Client.disconnect(self) + + + def send(self, what): + """Sends a jabber protocol element (Node) to the server""" + xmlstream.Client.write(self,str(what)) + + + def dispatch(self, root_node ): + """Called internally when a 'protocol element' is received. + Builds the relevant jabber.py object and dispatches it + to a relevant function or callback. + Also does some processing for roster and authentication + helper fuctions""" + self.DEBUG("dispatch called") + if root_node.name == 'message': + + self.DEBUG("got message dispatch") + msg_obj = Message(node=root_node) + self.messageHandler(msg_obj) + + elif root_node.name == 'presence': + + self.DEBUG("got presence dispatch") + pres_obj = Presence(node=root_node) + self.presenceHandler(pres_obj) + + elif root_node.name == 'iq': + + self.DEBUG("got an iq"); + iq_obj = Iq(node=root_node) + if root_node.getAttr('id') and \ + self._expected.has_key(root_node.getAttr('id')): + self._expected[root_node.getAttr('id')] = iq_obj + else: + self.iqHandler(iq_obj) + + else: + self.DEBUG("whats a tag -> " + root_node.name) + + ## Callback stuff ### + + def setMessageHandler(self, func, type='default', chainOutput=False): + """Sets the callback func for receiving messages. + Multiple callback functions can be set which are called in + succession. A type attribute can also be optionally passed so the + callback is only called when a message of this type is received. + + If 'chainOutput' is set to False (the default), the given function + should be defined as follows: + + def myMsgCallback(c, msg) + + Where the first parameter is the Client object, and the second + parameter is the Message object representing the message which was + received. + + If 'chainOutput' is set to True, the output from the various message + handler functions will be chained together. In this case, the given + callback function should be defined like this: + + def myMsgCallback(c, msg, output) + + Where 'output' is the value returned by the previous message + callback function. For the first callback routine, 'output' will be + set to an empty string. + """ + self.msg_hdlrs.append({ type : { 'cb' : func, + 'chain' : chainOutput} }) + + + def setPresenceHandler(self, func, type='default', chainOutput=False): + """Sets the callback func for receiving presence. + Multiple callback functions can be set which are called in + succession. A type attribute can also be optionally passed so the + callback is only called when a presence of this type is received. + + If 'chainOutput' is set to False (the default), the given function + should be defined as follows: + + def myPrCallback(c, p) + + Where the first parameter is the Client object, and the second + parameter is the Presence object representing the presence packet + which was received. + + If 'chainOutput' is set to True, the output from the various + presence handler functions will be chained together. In this case, + the given callback function should be defined like this: + + def myPrCallback(c, p, output) + + Where 'output' is the value returned by the previous presence + callback function. For the first callback routine, 'output' will be + set to an empty string. + """ + ## self.pres_hdlr = func + + self.pres_hdlrs.append({ type : { 'cb' : func, + 'chain' : chainOutput} }) + + + def setIqHandler(self, func, type='default', ns='default'): + """Sets the callback func for receiving iq. + Multiple callback functions can be set which are + called in succession. A type and namespace attribute + can also be set so set functions are only called for + iq elements with these properties. + + The given function should have two parameters, like this: + + def myIQCallback(c, iq) + + The first parameter will be set to the Client object, and the second + parameter will be set to the Iq object representing the IQ packet + which was received. + """ + self.iq_hdlrs.append({ type : { ns : func } }) + + + def setDisconnectHandler(self, func): + """Set the callback for a disconnect. + The given function will be called with a single parameter (the + connection object) when the connection is broken unexpectedly (eg, + in response to sending badly formed XML). self.lastErr and + self.lastErrCode will be set to the error which caused the + disconnection, if any. + """ + self.disconnect_hdlr = func + + + def messageHandler(self, msg_obj): ## Overide If You Want ## + """Called when a message protocol element is received - can be + overidden. """ + output = '' + type = msg_obj.getType() + for dicts in self.msg_hdlrs: + if dicts.has_key(type): + if dicts[type]['chain']: + output = dicts[type]['cb'](self, msg_obj, output) + else: + dicts[type]['cb'](self, msg_obj) + elif dicts.has_key('default'): + if dicts['default']['chain']: + output = dicts['default']['cb'](self, msg_obj, output) + else: + dicts['default']['cb'](self, msg_obj) + else: pass + + + def presenceHandler(self, pres_obj): ## Overide If You Want ## + """Called when a presence protocol element is received - can be + overidden. """ + output = '' + type = pres_obj.getType() + for dicts in self.pres_hdlrs: + if dicts.has_key(type): + if dicts[type]['chain']: + output = dicts[type]['cb'](self, pres_obj, output) + else: + dicts[type]['cb'](self, pres_obj) + elif dicts.has_key('default'): + if dicts['default']['chain']: + output = dicts['default']['cb'](self, pres_obj, output) + else: + dicts['default']['cb'](self, pres_obj) + else: pass + + + def iqHandler(self, iq_obj): ## Overide If You Want ## + """Called when an iq protocol element is received - can be + overidden""" + for dicts in self.iq_hdlrs: ## do stackables to check ## + if dicts.has_key(iq_obj.getType()): + if dicts[iq_obj.getType()].has_key(iq_obj.getQuery()): + dicts[iq_obj.getType()][iq_obj.getQuery()](self, iq_obj) + else: + dicts[iq_obj.getType()]['default'](self, iq_obj) + elif dicts.has_key('default'): + dicts['default']['default'](self, iq_obj) + else: pass + + + def disconnected(self): + """Called when a network error occurs - can be overidden""" + if self.disconnect_hdlr != None: self.disconnect_hdlr(self) + + ## functions for sending element with ID's ## + + def waitForResponse(self, ID, timeout=300): + """Blocks untils a protocol element with the given id is received. + If an error is received, waitForResponse returns None and + self.lastErr and self.lastErrCode is set to the received error. If + the operation times out (which only happens if a timeout value is + given), waitForResponse will return None and self.lastErr will be + set to "Timeout". + Changed default from timeout=0 to timeout=300 to avoid hangs in + scripts and such. + If you _really_ want no timeout, just set it to 0""" + ID = str(ID) + self._expected[ID] = None + has_timed_out = False + + abort_time = time.time() + timeout + if timeout: + self.DEBUG("waiting with timeout:%s for %s" % (timeout,str(ID))) + else: + self.DEBUG("waiting for %s" % str(ID)) + + while (not self._expected[ID]) and not has_timed_out: + self.process(0.2) + if timeout and (time.time() > abort_time): + has_timed_out = True + if has_timed_out: + self.lastErr = "Timeout" + return None + response = self._expected[ID] + del self._expected[ID] + if response.getErrorCode(): + self.lastErr = response.getError() + self.lastErrCode = response.getErrorCode() + return None + + return response + + + def SendAndWaitForResponse(self, obj, ID=None, timeout=300): + """Sends a protocol element object and blocks until a response with + the same ID is received. The received protocol object is returned + as the function result. """ + if ID is None : + ID = obj.getID() + if ID is None: + ID = self.getAnID() + obj.setID(ID) + ID = str(ID) + self.send(obj) + return self.waitForResponse(ID,timeout) + + + def getAnID(self): + """Returns a unique ID""" + self._id = self._id + 1 + return str(self._id) + +############################################################################# + +class Client(Connection): + """Class for managing a client connection to a jabber server.""" + def __init__(self, host, port=5222, debug=False, log=False, + connection=xmlstream.TCP ): + + Connection.__init__(self, host, port, NS_CLIENT, debug, log, + connection=connection) + + self._roster = Roster() + self._agents = {} + self._reg_info = {} + self._reg_agent = '' + + #xmlstream.Client.__init__(self, host, port, + # NS_CLIENT, debug, log) + + + def connect(self): + """Attempts to connect to the specified jabber server. + Raises an IOError on failure""" + self.DEBUG("jabberpy connect called") + try: + xmlstream.Client.connect(self) + except xmlstream.error, e: + raise IOError(e) + + + def disconnect(self): + """Safely disconnects from the connected server""" + self.send(Presence(type='unavailable')); + xmlstream.Client.disconnect(self) + + + def send(self, what): + """Sends a jabber protocol element (Node) to the server""" + xmlstream.Client.write(self,ustr(what)) + + + def sendInitPresence(self): + """Sends an empty presence protocol element to the + server. Used to inform the server that you are online""" + p = Presence() + self.send(p); + + + def dispatch(self, root_node ): + """Called internally when a protocol element is received. + Builds the relevant jabber.py object and dispatches it + to a relevant function or callback. + Also does some processing for roster and authentication + helper fuctions""" + self.DEBUG("dispatch called") + if root_node.name == 'message': + + self.DEBUG("got message dispatch") + msg_obj = Message(node=root_node) + self.messageHandler(msg_obj) + + elif root_node.name == 'presence': + + self.DEBUG("got presence dispatch") + pres_obj = Presence(node=root_node) + + who = str(pres_obj.getFrom()) + type = pres_obj.getType() + self.DEBUG("presence type is %s" % type) + if type == 'available' or not type: + self.DEBUG("roster setting %s to online" % who) + self._roster._setOnline(who,'online') + self._roster._setShow(who,pres_obj.getShow()) + self._roster._setStatus(who,pres_obj.getStatus()) + elif type == 'unavailable': + self._roster._setOnline(who,'offline') + self._roster._setShow(who,pres_obj.getShow()) + self._roster._setStatus(who,pres_obj.getStatus()) + else: + pass + self.presenceHandler(pres_obj) + + + elif root_node.name == 'iq': + + self.DEBUG("got an iq"); + iq_obj = Iq(node=root_node) + queryNS = iq_obj.getQuery() + + ## Tidy below up !! ## + type = root_node.getAttr('type') + + if queryNS: + + if queryNS == NS_ROSTER and ( type == 'result' \ + or type == 'set' ): + + for item in iq_obj.getQueryNode().getChildren(): + jid = item.getAttr('jid') + name = item.getAttr('name') + sub = item.getAttr('subscription') + ask = item.getAttr('ask') + + groups = [] + for group in item.getTags("group"): + groups.append(group.getData()) + + if jid: + if sub == 'remove' or sub == 'none': + self._roster._remove(jid) + else: + self._roster._set(jid=jid, name=name, + groups=groups, sub=sub, + ask=ask) + else: + self.DEBUG("roster - jid not defined ?") + + elif queryNS == NS_REGISTER and type == 'result': + + self._reg_info = {} + for item in iq_obj.getQueryNode().getChildren(): + self._reg_info[item.getName()] = item.getData() + + elif queryNS == NS_AGENTS and type == 'result': + + self.DEBUG("got agents result") + self._agents = {} + for agent in iq_obj.getQueryNode().getChildren(): + if agent.getName() == 'agent': ## hmmm + self._agents[agent.getAttr('jid')] = {} + for info in agent.getChildren(): + self._agents[agent.getAttr('jid')]\ + [info.getName()] = info.getData() + else: pass + + + if root_node.getAttr('id') and \ + self._expected.has_key(root_node.getAttr('id')): + self._expected[root_node.getAttr('id')] = iq_obj + else: + self.iqHandler(iq_obj) + + else: + self.DEBUG("whats a tag -> " + root_node.name) + + + def auth(self,username,passwd,resource): + """Authenticates and logs in to the specified jabber server + Automatically selects the 'best' authentication method + provided by the server. + Supports plain text, digest and zero-k authentication. + + Returns True if the login was successful, False otherwise. + """ + auth_get_iq = Iq(type='get') + auth_get_iq.setID('auth-get') + q = auth_get_iq.setQuery(NS_AUTH) + q.insertTag('username').insertData(username) + self.send(auth_get_iq) + + auth_response = self.waitForResponse("auth-get") + if auth_response == None: + return False # Error + else: + auth_ret_node = auth_response.asNode() + + auth_ret_query = auth_ret_node.getTag('query') + self.DEBUG("auth-get node arrived!") + + auth_set_iq = Iq(type='set') + auth_set_iq.setID('auth-set') + + q = auth_set_iq.setQuery(NS_AUTH) + q.insertTag('username').insertData(username) + q.insertTag('resource').insertData(resource) + + if auth_ret_query.getTag('token'): + + token = auth_ret_query.getTag('token').getData() + seq = auth_ret_query.getTag('sequence').getData() + self.DEBUG("zero-k authentication supported") + hash = sha.new(sha.new(passwd).hexdigest()+token).hexdigest() + for foo in xrange(int(seq)): hash = sha.new(hash).hexdigest() + q.insertTag('hash').insertData(hash) + + elif auth_ret_query.getTag('digest'): + + self.DEBUG("digest authentication supported") + digest = q.insertTag('digest') + digest.insertData(sha.new( + self.getIncomingID() + passwd).hexdigest() ) + else: + self.DEBUG("plain text authentication supported") + q.insertTag('password').insertData(passwd) + + iq_result = self.SendAndWaitForResponse(auth_set_iq) + + if iq_result.getError() is None: + return True + else: + self.lastErr = iq_result.getError() + self.lastErrCode = iq_result.getErrorCode() + # raise error(iq_result.getError()) ? + return False + if iq_result is None: + return False + return True + + ## Roster 'helper' func's - also see the Roster class ## + + def requestRoster(self): + """Requests the roster from the server and returns a + Roster() class instance.""" + rost_iq = Iq(type='get') + rost_iq.setQuery(NS_ROSTER) + self.SendAndWaitForResponse(rost_iq) + self.DEBUG("got roster response") + self.DEBUG("roster -> %s" % str(self._agents)) + return self._roster + + + def getRoster(self): + """Returns the current Roster() class instance. Does + not contact the server.""" + return self._roster + + + def addRosterItem(self, jid): + """ Send off a request to subscribe to the given jid. + """ + self.send(Presence(to=jid, type="subscribe")) + + + def updateRosterItem(self, jid, name=None, groups=None): + """ Update the information stored in the roster about a roster item. + + 'jid' is the Jabber ID of the roster entry; 'name' is the value to + set the entry's name to, and 'groups' is a list of groups to which + this roster entry can belong. If either 'name' or 'groups' is not + specified, that value is not updated in the roster. + """ + iq = Iq(type='set') + item = iq.setQuery(NS_ROSTER).insertTag('item') + item.putAtrr('jid', str(jid)) + if name != None: item.putAtrr('name', name) + if groups != None: + for group in groups: + item.insertTag('group').insertData(group) + dummy = self.sendAndWaitForResponse(iq) # Do we need to wait?? + + + def removeRosterItem(self,jid): + """Removes an item with Jabber ID jid from both the + server's roster and the local internal Roster() + instance""" + rost_iq = Iq(type='set') + q = rost_iq.setQuery(NS_ROSTER).insertTag('item') + q.putAttr('jid', str(jid)) + q.putAttr('subscription', 'remove') + self.SendAndWaitForResponse(rost_iq) + return self._roster + + ## Registration 'helper' funcs ## + + def requestRegInfo(self,agent=None): + """Requests registration info from the server. + Returns the Iq object received from the server.""" + if agent: agent = agent + '.' + if agent is None: agent = '' + self._reg_info = {} + self.DEBUG("agent -> %s, _host -> %s" % ( agent ,self._host)) + reg_iq = Iq(type='get', to = agent + self._host) + reg_iq.setQuery(NS_REGISTER) + self.DEBUG("got reg response") + self.DEBUG("roster -> %s" % str(self._agents)) + return self.SendAndWaitForResponse(reg_iq) + + + def getRegInfo(self): + """Returns a dictionary of fields requested by the server for a + registration attempt. Each dictionary entry maps from the name of + the field to the field's current value (either as returned by the + server or set programmatically by calling self.setRegInfo(). """ + return self._reg_info + + + def setRegInfo(self,key,val): + """Sets a name/value attribute. Note: requestRegInfo must be + called before setting.""" + self._reg_info[key] = val + + + def sendRegInfo(self, agent=None): + """Sends the populated registration dictionary back to the server""" + if agent: agent = agent + '.' + if agent is None: agent = '' + reg_iq = Iq(to = agent + self._host, type='set') + q = reg_iq.setQuery(NS_REGISTER) + for info in self._reg_info.keys(): + q.insertTag(info).putData(self._reg_info[info]) + return self.SendAndWaitForResponse(reg_iq) + + + def deregister(self, agent=None): + """ Send off a request to deregister with the server or with the given + agent. Returns True if successful, else False. + + Note that you must be authorised before attempting to deregister. + """ + if agent: agent = agent + '.' + if agent is None: agent = '' + q = self.requestRegInfo() + kids = q.getQueryPayload() + keyTag = kids.getTag("key") + + iq = Iq(to=agent+self._host, type="set") + iq.setQuery(NS_REGISTER) + iq.setQueryNode("") + q = iq.getQueryNode() + if keyTag != None: + q.insertXML("" + keyTag.getData() + "") + q.insertXML("") + + result = self.SendAndWaitForResponse(iq) + + if result == None: + return False + elif result.getType() == "result": + return True + else: + return False + + ## Agent helper funcs ## + + def requestAgents(self): + """Requests a list of available agents. Returns a dictionary + containing information about each agent; each entry in the + dictionary maps the agent's JID to a dictionary of attributes + describing what that agent can do (as returned by the + NS_AGENTS query).""" + self._agents = {} + agents_iq = Iq(type='get') + agents_iq.setQuery(NS_AGENTS) + self.SendAndWaitForResponse(agents_iq) + self.DEBUG("got agents response") + self.DEBUG("agents -> %s" % str(self._agents)) + return self._agents + +############################################################################# + +class Protocol: + """Base class for jabber 'protocol elements' - messages, presences and iqs. + Implements methods that are common to all these""" + def __init__(self): + self._node = None + + + def asNode(self): + """Returns an XMLStreamNode representation of the protocol element.""" + return self._node + + + def __str__(self): + return self._node.__str__() + + + def getTo(self): + """Returns the 'to' attribute as a JID object.""" + try: return JID(self._node.getAttr('to')) + except: return None + + + def getFrom(self): + """Returns the 'from' attribute as a JID object.""" + try: return JID(self._node.getAttr('from')) + except: return None + + + def getType(self): + """Returns the 'type' attribute of the protocol element.""" + try: return self._node.getAttr('type') + except: return None + + + def getID(self): + """Returns the 'id' attribute of the protocol element.""" + try: return self._node.getAttr('id') + except: return None + + + def setTo(self,val): + """Sets the 'to' element to the given JID.""" + self._node.putAttr('to', str(val)) + + + def setFrom(self,val): + """Sets the 'from' element to the given JID.""" + self._node.putAttr('from', str(val)) + + + def setType(self,val): + """Sets the 'type' attribute of the protocol element""" + self._node.putAttr('type', val) + + + def setID(self,val): + """Sets the ID of the protocol element""" + self._node.putAttr('id', val) + + + def getX(self,index=None): + """Returns the x namespace, optionally passed an index if there are + multiple tags.""" + ## TODO make it work for multiple x nodes + # jaclu 021231 commented out .namespace to get timestamps working + try: return self._node.getTag('x') #.namespace + except: return None + + + def setX(self,namespace,index=None): + """Sets the name space of the x tag. It also creates the node + if it doesn't already exist.""" + ## TODO make it work for multiple x nodes + x = self._node.getTag('x') + if x: + x.namespace = namespace + else: + x = self._node.insertTag('x') + x.setNamespace(namespace) + return x + + + def setXPayload(self, payload): + """Sets the Child of an 'x' tag. Can be a Node instance or an + XML document""" + x = self._node.insertTag('x') + + if type(payload) == type('') or type(payload) == type(u''): + payload = xmlstream.NodeBuilder(payload).getDom() + + x.kids = [] # should be a method for this realy + x.insertNode(payload) + + + def getXPayload(self, val=None): + """Returns the x tags' payload as a list of Node instances.""" + nodes = [] + if val is not None: + if type(val) == type(""): + for xnode in self._node.getTags('x'): + if xnode.getNamespace() == val: nodes.append(xnode.kids[0]) + return nodes + else: + try: return self._node.getTags('x')[val].kids[0] + except: return None + + for xnode in self._node.getTags('x'): + nodes.append(xnode.kids[0]) + return nodes + + + def getXNode(self, val=None): + """Returns the x Node instance. If there are multiple tags + the first Node is returned. For multiple X nodes use getXNodes + or pass an index integer value or namespace string to getXNode + and if a match is found it will be returned.""" + if val is not None: + nodes = [] + if type(val) == type(""): + for xnode in self._node.getTags('x'): + if xnode.getNamespace() == val: nodes.append(xnode) + return nodes + else: + try: return self._node.getTags('x')[val] + except: return None + else: + try: return self._node.getTag('x') + except: return None + + + def getXNodes(self, val=None): + """Returns a list of X nodes.""" + try: return self._node.getTags('x')[val] + except: return None + + + def setXNode(self, val=''): + """Sets the x tag's data to the given textual value.""" + self._node.insertTag('x').putData(val) + + + def fromTo(self): + """Swaps the element's from and to attributes. + Note that this is only useful for writing components; if you are + writing a Jabber client you shouldn't use this, because the Jabber + server will set the 'from' field automatically.""" + tmp = self.getTo() + self.setTo(self.getFrom()) + self.setFrom(tmp) + + __repr__ = __str__ + +############################################################################# + +class Message(Protocol): + """Builds on the Protocol class to provide an interface for sending + message protocol elements""" + def __init__(self, to=None, body=None, node=None): + self.time_stamp = None + if node: + self._node = node + # examine x tag and set timestamp if pressent + x = self._node.getTag('x') + if x: + ts = x.getAttr('stamp') + if ts: + self.setTimestamp( ts ) + else: + self._node = xmlstream.Node(tag='message') + if to: self.setTo(str(to)) + if body: self.setBody(body) + + + def getBody(self): + """Returns the message body.""" + body = self._node.getTag('body') + try: return self._node.getTag('body').getData() + except: return None + + + def getSubject(self): + """Returns the message's subject.""" + try: return self._node.getTag('subject').getData() + except: return None + + + def getThread(self): + """Returns the message's thread ID.""" + try: return self._node.getTag('thread').getData() + except: return None + + + def getError(self): + """Returns the message's error string, if any.""" + try: return self._node.getTag('error').getData() + except: return None + + + def getErrorCode(self): + """Returns the message's error Code, if any.""" + try: return self._node.getTag('error').getAttr('code') + except: return None + + + def getTimestamp(self): + return self.time_stamp + + + def setBody(self,val): + """Sets the message body text.""" + body = self._node.getTag('body') + if body: + body.putData(val) + else: + body = self._node.insertTag('body').putData(val) + + + def setSubject(self,val): + """Sets the message subject text.""" + subj = self._node.getTag('subject') + if subj: + subj.putData(val) + else: + self._node.insertTag('subject').putData(val) + + + def setThread(self,val): + """Sets the message thread ID.""" + thread = self._node.getTag('thread') + if thread: + thread.putData(val) + else: + self._node.insertTag('thread').putData(val) + + + def setError(self,val,code): + """Sets the message error text.""" + err = self._node.getTag('error') + if err: + err.putData(val) + else: + err = self._node.insertTag('error') + err.putData(val) + err.putAttr('code',str(code)) + + + def setTimestamp(self,val): + if not val: + val = time.strftime( '%Y%m%dT%H:%M:%S', time.gmtime( time.time())) + self.time_stamp = val + + + def build_reply(self, reply_txt=''): + """Returns a new Message object as a reply to itself. + The reply message has the 'to', 'type' and 'thread' attributes + automatically set.""" + m = Message(to=self.getFrom(), body=reply_txt) + if not self.getType() == None: + m.setType(self.getType()) + t = self.getThread() + if t: m.setThread(t) + return m + +############################################################################# + +class Presence(Protocol): + """Class for creating and managing jabber protocol + elements""" + def __init__(self, to=None, type=None, node=None): + if node: + self._node = node + else: + self._node = xmlstream.Node(tag='presence') + if to: self.setTo(str(to)) + if type: self.setType(type) + + + def getStatus(self): + """Returns the presence status""" + try: return self._node.getTag('status').getData() + except: return None + + + def getShow(self): + """Returns the presence show""" + try: return self._node.getTag('show').getData() + except: return None + + + def getPriority(self): + """Returns the presence priority""" + try: return self._node.getTag('priority').getData() + except: return None + + + def setShow(self,val): + """Sets the presence show""" + show = self._node.getTag('show') + if show: + show.putData(val) + else: + self._node.insertTag('show').putData(val) + + + def setStatus(self,val): + """Sets the presence status""" + status = self._node.getTag('status') + if status: + status.putData(val) + else: + self._node.insertTag('status').putData(val) + + + def setPriority(self,val): + """Sets the presence priority""" + pri = self._node.getTag('priority') + if pri: + pri.putData(val) + else: + self._node.insertTag('priority').putData(val) + +############################################################################# + +class Iq(Protocol): + """Class for creating and managing jabber protocol + elements""" + def __init__(self, to='', type=None, node=None): + if node: + self._node = node + else: + self._node = xmlstream.Node(tag='iq') + if to: self.setTo(to) + if type: self.setType(type) + + + def getError(self): + """Returns the Iq's error string, if any""" + try: return self._node.getTag('error').getData() + except: return None + + + def getErrorCode(self): + """Returns the Iq's error code, if any""" + try: return self._node.getTag('error').getAttr('code') + except: return None + + + def setError(self,val,code): + """Sets an Iq's error string and code""" + err = self._node.getTag('error') + if err: + err.putData(val) + else: + err = self._node.insertTag('error') + err.putData(val) + err.putAttr('code',str(code)) + + + def _getTag(self,tag): + try: return self._node.getTag(tag).namespace + except: return None + + def _setTag(self,tag,namespace): + q = self._node.getTag(tag) + if q: + q.namespace = namespace + else: + q = self._node.insertTag(tag) + q.setNamespace(namespace) + return q + + + def getList(self): + "returns the list namespace" + return self._getTag('list') + + def setList(self,namespace): + return self._setTag('list',namespace) + + + def getQuery(self): + "returns the query namespace" + return self._getTag('query') + + def setQuery(self,namespace): + """Sets a query's namespace, and inserts a query tag if + one doesn't already exist. The resulting query tag + is returned as the function result.""" + return self._setTag('query',namespace) + + + def setQueryPayload(self, payload): + """Sets a Iq's query payload. 'payload' can be either a Node + structure or a valid xml document. The query tag is automatically + inserted if it doesn't already exist.""" + q = self.getQueryNode() + + if q is None: + q = self._node.insertTag('query') + + if type(payload) == type('') or type(payload) == type(u''): + payload = xmlstream.NodeBuilder(payload).getDom() + + q.kids = [] + q.insertNode(payload) + + + def getQueryPayload(self): + """Returns the query's payload as a Node instance""" + q = self.getQueryNode() + if q: + return q.kids[0] + return None + + + def getQueryNode(self): + """Returns any textual data contained by the query tag""" + try: return self._node.getTag('query') + except: return None + + + def setQueryNode(self, val): + """Sets textual data contained by the query tag""" + q = self._node.getTag('query') + if q: + q.putData(val) + else: + self._node.insertTag('query').putData(val) + +############################################################################# + +class Roster: + """A Class for simplifying roster management. Also tracks roster + item availability.""" + def __init__(self): + self._data = {} + self._listener = None + ## unused for now ... ## + self._lut = { 'both':RS_SUB_BOTH, + 'from':RS_SUB_FROM, + 'to':RS_SUB_TO } + + + def setListener(self, listener): + """ Set a listener function to be called whenever the roster changes. + + The given function will be called whenever the contents of the + roster changes in response to a received or packet. + The listener function should be defined as follows: + + def listener(action, jid, info) + + 'action' is a string indicating what type of change has occurred: + + "add" A new item has been added to the roster. + "update" An existing roster item has been updated. + "remove" A roster entry has been removed. + + 'jid' is the Jabber ID (as a string) of the affected roster entry. + + 'info' is a dictionary containing the information that has been + added or updated for this roster entry. This dictionary may + contain any combination of the following: + + "name" The associated name of this roster entry. + "groups" A list of groups associated with this roster entry. + "online" The roster entry's "online" value ("online", + "offline" or "pending"). + "sub" The roster entry's subscription value ("none", + "from", "to" or "both"). + "ask" The roster entry's ask value, if any (None, + "subscribe", "unsubscribe"). + "show" The roster entry's show value, if any (None, "away", + "chat", "dnd", "normal", "xa"). + "status" The roster entry's current 'status' value, if + specified. + """ + self._listener = listener + + + def getStatus(self, jid): ## extended + """Returns the 'status' value for a Roster item with the given jid.""" + jid = str(jid) + if self._data.has_key(jid): + return self._data[jid]['status'] + return None + + + def getShow(self, jid): ## extended + """Returns the 'show' value for a Roster item with the given jid.""" + jid = str(jid) + if self._data.has_key(jid): + return self._data[jid]['show'] + return None + + + def getOnline(self,jid): ## extended + """Returns the 'online' status for a Roster item with the given jid. + """ + jid = str(jid) + if self._data.has_key(jid): + return self._data[jid]['online'] + return None + + + def getSub(self,jid): + """Returns the 'subscription' status for a Roster item with the given + jid.""" + jid = str(jid) + if self._data.has_key(jid): + return self._data[jid]['sub'] + return None + + + def getName(self,jid): + """Returns the 'name' for a Roster item with the given jid.""" + jid = str(jid) + if self._data.has_key(jid): + return self._data[jid]['name'] + return None + + + def getGroups(self,jid): + """ Returns the lsit of groups associated with the given roster item. + """ + jid = str(jid) + if self._data.has_key(jid): + return self._data[jid]['groups'] + return None + + + def getAsk(self,jid): + """Returns the 'ask' status for a Roster item with the given jid.""" + jid = str(jid) + if self._data.has_key(jid): + return self._data[jid]['ask'] + return None + + + def getSummary(self): + """Returns a summary of the roster's contents. The returned value is a + dictionary mapping the basic (no resource) JIDs to their current + availability status (online, offline, pending). """ + to_ret = {} + for jid in self._data.keys(): + to_ret[jid] = self._data[jid]['online'] + return to_ret + + + def getJIDs(self): + """Returns a list of JIDs stored within the roster. Each entry in the + list is a JID object.""" + to_ret = []; + for jid in self._data.keys(): + to_ret.append(JID(jid)) + return to_ret + + + def getRaw(self): + """Returns the internal data representation of the roster.""" + return self._data + + + def isOnline(self,jid): + """Returns True if the given jid is online, False if not.""" + jid = str(jid) + if self.getOnline(jid) != 'online': + return False + else: + return True + + + def _set(self,jid,name,groups,sub,ask): + # meant to be called by actual iq tag + """Used internally - private""" + jid = str(jid) # just in case + online = 'offline' + if ask: online = 'pending' + if self._data.has_key(jid): # update it + self._data[jid]['name'] = name + self._data[jid]['groups'] = groups + self._data[jid]['ask'] = ask + self._data[jid]['sub'] = sub + if self._listener != None: + self._listener("update", jid, {'name' : name, + 'groups' : groups, + 'sub' : sub, 'ask' : ask}) + else: + self._data[jid] = { 'name': name, 'groups' : groups, 'ask': ask, + 'sub': sub, 'online': online, 'status': None, + 'show': None} + if self._listener != None: + self._listener("add", jid, {'name' : name, 'groups' : groups, + 'sub' : sub, 'ask' : ask, + 'online' : online}) + + + def _setOnline(self,jid,val): + """Used internally - private""" + jid = str(jid) + if self._data.has_key(jid): + self._data[jid]['online'] = val + if self._listener != None: + self._listener("update", jid, {'online' : val}) + else: ## fall back + jid_basic = JID(jid).getStripped() + if self._data.has_key(jid_basic): + self._data[jid_basic]['online'] = val + if self._listener != None: + self._listener("update", jid_basic, {'online' : val}) + + + def _setShow(self,jid,val): + """Used internally - private""" + jid = str(jid) + if self._data.has_key(jid): + self._data[jid]['show'] = val + if self._listener != None: + self._listener("update", jid, {'show' : val}) + else: ## fall back + jid_basic = JID(jid).getStripped() + if self._data.has_key(jid_basic): + self._data[jid_basic]['show'] = val + if self._listener != None: + self._listener("update", jid_basic, {'show' : val}) + + + def _setStatus(self,jid,val): + """Used internally - private""" + jid = str(jid) + if self._data.has_key(jid): + self._data[jid]['status'] = val + if self._listener != None: + self._listener("update", jid, {'status' : val}) + else: ## fall back + jid_basic = JID(jid).getStripped() + if self._data.has_key(jid_basic): + self._data[jid_basic]['status'] = val + if self._listener != None: + self._listener("update", jid_basic, {'status' : val}) + + + def _remove(self,jid): + """Used internally - private""" + if self._data.has_key(jid): + del self._data[jid] + if self._listener != None: + self._listener("remove", jid, {}) + +############################################################################# + +class JID: + """A Simple class for managing jabber users id's """ + def __init__(self, jid='', node='', domain='', resource=''): + if jid: + if find(jid, '@') == -1: + self.node = '' + else: + bits = split(jid, '@',1) + self.node = bits[0] + jid = bits[1] + + if find(jid, '/') == -1: + self.domain = jid + self.resource = '' + else: + self.domain, self.resource = split(jid, '/',1) + else: + self.node = node + self.domain = domain + self.resource = resource + + + def __str__(self): + try: + jid_str = '' + if self.node: jid_str = jid_str + self.node + '@' + if self.domain: jid_str = jid_str + self.domain + if self.resource: jid_str = jid_str +'/'+ self.resource + return jid_str + except: + return '' + + __repr__ = __str__ + + + def getBasic(self): + """Returns a jid string with no resource""" + return self.node + '@' + self.domain + + def getNode(self): + """Returns JID Node as string""" + return self.node + + + def getDomain(self): + """Returns JID domain as string""" + return self.domain + + + def getResource(self): + """Returns JID resource as string""" + return self.resource + + + def setNode(self,val): + """Sets JID Node from string""" + self.node = val + + + def setDomain(self,val): + """Sets JID domain from string""" + self.domain = val + + + def setResource(self,val): + """Sets JID resource from string""" + self.resource = val + + + def getStripped(self): + """Returns a jid string with no resource""" + jid_str = '' + if self.node: jid_str = jid_str + self.node + '@' + if self.domain: jid_str = jid_str + self.domain + return jid_str + +############################################################################# + +## component types + +## Accept NS_COMP_ACCEPT +## Connect NS_COMP_CONNECT +## Execute NS_COMP_EXECUTE + +class Component(Connection): + """docs to come soon... """ + def __init__(self, host, port=5222, connection=xmlstream.TCP, + debug=False, log=False, ns=NS_COMP_ACCEPT): + # EJW: Does it make sense to have a default port here? Components, by + # definition, will use a port different from the standard Jabber client + # connection port, so the above is misleading... + + self._auth_OK = False + + Connection.__init__(self, host, port, + namespace=ns, + debug=debug, + log=log, + connection=connection) + + + def auth(self,secret): + """will disconnect on failure""" + self.send( u"%s" + % sha.new( self.getIncomingID() + secret ).hexdigest() + ) + while not self._auth_OK: + self.DEBUG("waiting on handshake") + self.process(1) + + return True + + + def dispatch(self, root_node): + """Catch the here""" + if root_node.name == 'handshake': # check id too ? + self._auth_OK = True + Connection.dispatch(self, root_node) + +############################################################################# + +## component protocol elements + +class XDB(Protocol): + + def __init__(self, to='', frm='', type=None, node=None): + if node: + self._node = node + else: + self._node = xmlstream.Node(tag='xdb') + if to: self.setTo(to) + if type: self.setType(type) + if frm: self.setFrom(type) + +############################################################################# + +class Log(Protocol): + ## eg: Hello Log File + + def __init__(self, to='', frm='', type=None, node=None): + if node: + self._node = node + else: + self._node = xmlstream.Node(tag='log') + if to: self.setTo(to) + if type: self.setType(type) + if frm: self.setFrom(type) + + + def setBody(self,val): + "Sets the log message text." + self._node.getTag('log').putData(val) + + + def setBody(self): + "Returns the log message text." + return self._node.getTag('log').getData() + +############################################################################# + +class Server: + pass + + diff --git a/common/optparser.py b/common/optparser.py new file mode 100644 index 000000000..6bdda65e0 --- /dev/null +++ b/common/optparser.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +## common/optparser.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - David Ferlier +## +## Copyright (C) 2003 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import ConfigParser +import logging + +log = logging.getLogger('common.options') + +class OptionsParser: + def __init__(self, fname): + self.__fname = fname + # END __init__ + + def parseCfgFile(self): + try: + self.__fd = open(self.__fname) + except: + print 'error cannot open file %s\n' % (self.__fname); + return + + self.__config = ConfigParser.ConfigParser() + self.__config.readfp(self.__fd) + self.__sections = self.__config.sections() + + for section in self.__sections: + for option in self.__config.options(section): + value = self.__config.get(section, option, 1) + setattr(self, str(section) + '_' + \ + str(option), value) + # END parseCfgFile + + def __str__(self): + return "OptionsParser" + # END __str__ + + def __getattr__(self, attr): + if attr.startswith('__') and attr in self.__dict__.keys(): + return self.__dict__[attr] + else: + for key in self.__dict__.keys(): + if key == attr: + return self.__dict__[attr] + return None + # END __getattr__ + + def writeCfgFile(self): + try: + self.__config.write(open(self.__fname, 'w')) + except: + log.debug("Can't write config %s" % self.__fname) + return 0 + return 1 + # END writeCfgFile + + def stop(self): + return self.writeCfgFile() + # END stop +# END OptionsParser diff --git a/common/plugin.py b/common/plugin.py new file mode 100644 index 000000000..c1aad711f --- /dev/null +++ b/common/plugin.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +## common/plugin.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - David Ferlier +## +## Copyright (C) 2003 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import common.thread + +""" Plugin definitions """ + +class GajimPlugin: + def __init__(self, name, queueIn, queueOut): + """ queueIn is a queue to interact from the hub to the plugin """ + self.name = name + self.queueIn = queueIn + self.queueOut= queueOut + # END __init__ + + def load(self): + self.thr = common.thread.GajimThread(self.name, self.queueIn, \ + self.queueOut) + # END load +# END GajimPlugin diff --git a/common/thread.py b/common/thread.py new file mode 100644 index 000000000..5da21e14a --- /dev/null +++ b/common/thread.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +## common/thread.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - David Ferlier +## +## Copyright (C) 2003 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import threading +import socket +import sys +import time +import plugins + +class GajimThread(threading.Thread): + def __init__(self, name = None, queueIn = None, queueOut = None): + self.queueIn = queueIn + self.queueOut = queueOut + threading.Thread.__init__(self, target = self.run, \ + name = name, args = () ) + self.start() + # END __init__ + + def run(self): + if self.getName() == 'gtkgui': + plugins.gtkgui.plugin(self.queueIn, self.queueOut) + # END run +# END GajimThread diff --git a/common/xmlstream.py b/common/xmlstream.py new file mode 100644 index 000000000..31f48ef50 --- /dev/null +++ b/common/xmlstream.py @@ -0,0 +1,605 @@ +## xmlstream.py +## +## Copyright (C) 2001 Matthew Allum +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU Lesser General Public License as published +## by the Free Software Foundation; either version 2, or (at your option) +## any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Lesser General Public License for more details. + + +"""\ +xmlstream.py provides simple functionality for implementing +XML stream based network protocols. It is used as a base +for jabber.py. + +xmlstream.py manages the network connectivity and xml parsing +of the stream. When a complete 'protocol element' ( meaning a +complete child of the xmlstreams root ) is parsed the dipatch +method is called with a 'Node' instance of this structure. +The Node class is a very simple XML DOM like class for +manipulating XML documents or 'protocol elements' in this +case. + +""" + +# $Id: xmlstream.py,v 1.26 2003/02/20 10:22:33 shire Exp $ + +import site +site.encoding = 'UTF-8' +import time, sys, re, socket +from select import select +from string import split,find,replace,join +import xml.parsers.expat + +VERSION = 0.3 + +False = 0 +True = 1 + +TCP = 1 +STDIO = 0 +TCP_SSL = 2 + +ENCODING = site.encoding + +BLOCK_SIZE = 1024 ## Number of bytes to get at at time via socket + ## transactions + + +def XMLescape(txt): + "Escape XML entities" + txt = replace(txt, "&", "&") + txt = replace(txt, "<", "<") + txt = replace(txt, ">", ">") + return txt + +def XMLunescape(txt): + "Unescape XML entities" + txt = replace(txt, "<", "<") + txt = replace(txt, ">", ">") + txt = replace(txt, "&", "&") + return txt + +class error: + def __init__(self, value): + self.value = str(value) + def __str__(self): + return self.value + +class Node: + """A simple XML DOM like class""" + def __init__(self, tag='', parent=None, attrs=None ): + bits = split(tag) + if len(bits) == 1: + self.name = tag + self.namespace = '' + else: + self.namespace, self.name = bits + + if attrs is None: + self.attrs = {} + else: + self.attrs = attrs + + self.data = [] + self.kids = [] + self.parent = parent + + def setParent(self, node): + "Set the nodes parent node." + self.parent = node + + def getParent(self): + "return the nodes parent node." + return self.parent + + def getName(self): + "Set the nodes tag name." + return self.name + + def setName(self,val): + "Set the nodes tag name." + self.name = val + + def putAttr(self, key, val): + "Add a name/value attribute to the node." + self.attrs[key] = val + + def getAttr(self, key): + "Get a value for the nodes named attribute." + try: return self.attrs[key] + except: return None + + def putData(self, data): + "Set the nodes textual data" + self.data.append(data) + + def insertData(self, data): + "Set the nodes textual data" + self.data.append(data) + + def getData(self): + "Return the nodes textual data" + return join(self.data, '') + + def getDataAsParts(self): + "Return the node data as an array" + return self.data + + def getNamespace(self): + "Returns the nodes namespace." + return self.namespace + + def setNamespace(self, namespace): + "Set the nodes namespace." + self.namespace = namespace + + def insertTag(self, name): + """ Add a child tag of name 'name' to the node. + + Returns the newly created node. + """ + newnode = Node(tag=name, parent=self) + self.kids.append(newnode) + return newnode + + def insertNode(self, node): + "Add a child node to the node" + self.kids.append(node) + return node + + def insertXML(self, xml_str): + "Add raw xml as a child of the node" + newnode = NodeBuilder(xml_str).getDom() + self.kids.append(newnode) + return newnode + + def __str__(self): + return self._xmlnode2str() + + def _xmlnode2str(self, parent=None): + """Returns an xml ( string ) representation of the node + and it children""" + s = "<" + self.name + if self.namespace: + if parent and parent.namespace != self.namespace: + s = s + " xmlns = '%s' " % self.namespace + for key in self.attrs.keys(): + val = str(self.attrs[key]) + s = s + " %s='%s'" % ( key, XMLescape(val) ) + s = s + ">" + cnt = 0 + if self.kids != None: + for a in self.kids: + if (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt]) + s = s + a._xmlnode2str(parent=self) + cnt=cnt+1 + if (len(self.data)-1) >= cnt: s = s + XMLescape(self.data[cnt]) + s = s + "" + return s + + def getTag(self, name): + """Returns a child node with tag name. Returns None + if not found.""" + for node in self.kids: + if node.getName() == name: + return node + return None + + def getTags(self, name): + """Like getTag but returns a list with matching child nodes""" + nodes=[] + for node in self.kids: + if node.getName() == name: + nodes.append(node) + return nodes + + + def getChildren(self): + """Returns a nodes children""" + return self.kids + +class NodeBuilder: + """builds a 'minidom' from data parsed to it. Primarily for insertXML + method of Node""" + def __init__(self,data): + self._parser = xml.parsers.expat.ParserCreate(namespace_separator=' ') + self._parser.StartElementHandler = self.unknown_starttag + self._parser.EndElementHandler = self.unknown_endtag + self._parser.CharacterDataHandler = self.handle_data + + self.__depth = 0 + self.__done = 0 #needed ? + self.__space_regex = re.compile('^\s+$') + + self._parser.Parse(data,1) + + def unknown_starttag(self, tag, attrs): + self.__depth = self.__depth + 1 + if self.__depth == 1: + self._mini_dom = Node(tag=tag, attrs=attrs) + self._ptr = self._mini_dom + elif self.__depth > 1: + self._ptr.kids.append(Node(tag =tag, + parent=self._ptr, + attrs =attrs )) + self._ptr = self._ptr.kids[-1] + else: ## fix this .... + pass + + def unknown_endtag(self, tag ): + self.__depth = self.__depth - 1 + if self.__depth == 0: + self.dispatch(self._mini_dom) + elif self.__depth > 0: + self._ptr = self._ptr.parent + else: + pass + + def handle_data(self, data): + if not self.__space_regex.match(data): ## check its not all blank + self._ptr.data.append(data) + + def dispatch(self,dom): + self.__done = 1 + + def getDom(self): + return self._mini_dom + + +class Stream: + def __init__( + self, host, port, namespace, + debug=True, + log=None, + sock=None, + id=None, + connection=TCP + ): + + + self._parser = xml.parsers.expat.ParserCreate(namespace_separator=' ') + self._parser.StartElementHandler = self._unknown_starttag + self._parser.EndElementHandler = self._unknown_endtag + self._parser.CharacterDataHandler = self._handle_data + + self._host = host + self._port = port + self._namespace = namespace + self.__depth = 0 + self._sock = sock + + self._sslObj = None + self._sslIssuer = None + self._sslServer = None + + self._incomingID = None + self._outgoingID = id + + self._debug = debug + self._connection=connection + + self.DEBUG("stream init called") + + if log: + if type(log) is type(""): + try: + self._logFH = open(log,'w') + except: + print "ERROR: can open %s for writing" + sys.exit(0) + else: ## assume its a stream type object + self._logFH = log + else: + self._logFH = None + self._timestampLog = True + + def timestampLog(self,timestamp): + """ Enable or disable the showing of a timestamp in the log. + By default, timestamping is enabled. + """ + self._timestampLog = timestamp + + def DEBUG(self,txt): + if self._debug: + try: + sys.stderr.write("DEBUG: %s\n" % txt) + except: + # unicode strikes again ;) + s=u'' + for i in range(len(txt)): + if ord(txt[i]) < 128: + c = txt[i] + else: + c = '?' + s=s+c + sys.stderr.write("DEBUG: %s\n" % s ) + + def getSocket(self): + return self._sock + + def header(self): + self.DEBUG("stream: sending initial header") + str = u" \ + " + self.write (str) + self.read() + + def _handle_data(self, data): + """XML Parser callback""" + self.DEBUG("data-> " + data) + ## TODO: get rid of empty space + ## self._ptr.data = self._ptr.data + data + self._ptr.data.append(data) + + def _unknown_starttag(self, tag, attrs): + """XML Parser callback""" + self.__depth = self.__depth + 1 + self.DEBUG("DEPTH -> %i , tag -> %s, attrs -> %s" % \ + (self.__depth, tag, str(attrs)) ) + if self.__depth == 2: + self._mini_dom = Node(tag=tag, attrs=attrs) + self._ptr = self._mini_dom + elif self.__depth > 2: + self._ptr.kids.append(Node(tag=tag,parent=self._ptr,attrs=attrs)) + self._ptr = self._ptr.kids[-1] + else: ## it the stream tag: + if attrs.has_key('id'): + self._incomingID = attrs['id'] + + def _unknown_endtag(self, tag ): + """XML Parser callback""" + self.__depth = self.__depth - 1 + self.DEBUG("DEPTH -> %i" % self.__depth) + if self.__depth == 1: + self.dispatch(self._mini_dom) + elif self.__depth > 1: + self._ptr = self._ptr.parent + else: + self.DEBUG("*** Server closed connection ? ****") + + def dispatch(self, nodes, depth = 0): + """Overide with the method you want to called with + a node structure of a 'protocol element.""" + + padding = ' ' + padding = padding * depth + depth = depth + 1 + for n in nodes: + if n.kids != None: + self.dispatch(n.kids, depth) + + ##def syntax_error(self, message): + ## self.DEBUG("error " + message) + + def _do_read( self, action, buff_size ): + """workhorse for read() method. + + added 021231 by jaclu""" + data='' + data_in = action(buff_size) + while data_in: + data = data + data_in + if len(data_in) != buff_size: + break + data_in = action(buff_size) + return data + + def read(self): + """Reads incoming data. Called by process() so nonblocking + + changed 021231 by jaclu + """ + if self._connection == TCP: + raw_data = self._do_read(self._sock.recv, BLOCK_SIZE) + elif self._connection == TCP_SSL: + raw_data = self._do_read(self._sslObj.read, BLOCK_SIZE) + elif self._connection == STDIO: + raw_data = self._do_read(self.stdin.read, 1024) + else: + raw_data = '' # should never get here + + # just encode incoming data once! + data = unicode(raw_data,'utf-8').encode(ENCODING,'replace') + self.DEBUG("got data %s" % data ) + self.log(data, 'RECV:') + self._parser.Parse(data) + return data + + def write(self,raw_data=u''): + """Writes raw outgoing data. blocks + + changed 021231 by jaclu, added unicode encoding + """ + if type(raw_data) == type(u''): + data_out = raw_data.encode('utf-8','replace') + else: + # since not suplied as unicode, we must guess at + # what the data is, iso-8859-1 seems reasonable. + # To avoid this auto assumption, + # send your data as a unicode string! + data_out = unicode(raw_data,'iso-8859-1').encode(ENCODING,'replace') + try: + if self._connection == TCP: + self._sock.send (data_out) + elif self._connection == TCP_SSL: + self._sslObj.write(data_out) + elif self._connection == STDIO: + self.stdout.write(data_out) + else: + pass + self.log(data_out, 'SENT:') + self.DEBUG("sent %s" % data_out) + except: + self.DEBUG("xmlstream write threw error") + self.disconnected() + + def process(self,timeout): + + reader=Node + + if self._connection == TCP: + reader = self._sock + elif self._connection == TCP_SSL: + reader = self._sock + elif self._connection == STDIO: + reader = sys.stdin + else: + pass + + ready_for_read,ready_for_write,err = \ + select( [reader],[],[],timeout) + for s in ready_for_read: + if s == reader: + if not len(self.read()): # length of 0 means disconnect + ## raise error("network error") ? + self.disconnected() + return False + return True + return False + + def disconnect(self): + """Close the stream and socket""" + self.write ( "" ) + self._sock.close() + self._sock = None + + def disconnected(self): ## To be overidden ## + """Called when a Network Error or disconnection occurs. + Designed to be overidden""" + self.DEBUG("Network Disconnection") + pass + + def log(self, data, inout=''): + """Logs data to the specified filehandle. Data is time stamped + and prefixed with inout""" + if self._logFH is not None: + if self._timestampLog: + self._logFH.write("%s - %s - %s\n" % (time.asctime(), inout, data)) + else: + self._logFH.write("%s - %s\n" % (inout, data ) ) + self._logFH.flush() + + def getIncomingID(self): + """Returns the streams ID""" + return self._incomingID + + def getOutgoingID(self): + """Returns the streams ID""" + return self._incomingID + + +class Client(Stream): + + def connect(self): + """Attempt to connect to specified host""" + + self.DEBUG("client connect called to %s %s type %i" % (self._host, + self._port, + self._connection) ) + + ## TODO: check below that stdin/stdout are actually open + if self._connection == STDIO: return + + self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + self._sock.connect((self._host, self._port)) + except socket.error, e: + self.DEBUG("socket error") + raise error(e) + + if self._connection == TCP_SSL: + try: + self.DEBUG("Attempting to create ssl socket") + self._sslObj = socket.ssl( self._sock, None, None ) + self._sslIssuer = self._sslObj.issuer() + self._sslServer = self._sslObj.server() + except: + self.DEBUG("Socket Error: No SSL Support") + raise error("No SSL Support") + + self.DEBUG("connected") + self.header() + return 0 + +class Server: + + def now(self): return time.ctime(time.time()) + + def __init__(self, maxclients=10): + + self.host = '' + self.port = 5222 + self.streams = [] + + # make main sockets for accepting new client requests + self.mainsocks, self.readsocks, self.writesocks = [], [], [] + + self.portsock = socket(AF_INET, SOCK_STREAM) + self.portsock.bind((self.host, self.port)) + self.portsock.listen(maxclients) + + self.mainsocks.append(self.portsock) # add to main list to identify + self.readsocks.append(self.portsock) # add to select inputs list + + # event loop: listen and multiplex until server process killed + + + def serve(self): + + print 'select-server loop starting' + + while 1: + print "LOOPING" + readables, writeables, exceptions = select(self.readsocks, + self.writesocks, []) + for sockobj in readables: + if sockobj in self. mainsocks: # for ready input sockets + newsock, address = sockobj.accept() # accept not block + print 'Connect:', address, id(newsock) + self.readsocks.append(newsock) + self._makeNewStream(newsock) + # add to select list, wait + else: + # client socket: read next line + data = sockobj.recv(1024) + # recv should not block + print '\tgot', data, 'on', id(sockobj) + if not data: # if closed by the clients + sockobj.close() # close here and remv from + self.readsocks.remove(sockobj) + else: + # this may block: should really select for writes too + sockobj.send('Echo=>%s' % data) + + def _makeNewStream(self, sckt): + new_stream = Stream('localhost', 5222, + 'jabber:client', + sock=sckt) + self.streams.append(new_stream) + ## maybe overide for a 'server stream' + new_stream.header() + return new_stream + + def _getStreamSockets(self): + socks = []; + for s in self.streams: + socks.append(s.getSocket()) + return socks + + def _getStreamFromSocket(self, sock): + for s in self.streams: + if s.getSocket() == sock: + return s + return None + diff --git a/doc/BUGS b/doc/BUGS new file mode 100644 index 000000000..7be51d4f8 --- /dev/null +++ b/doc/BUGS @@ -0,0 +1 @@ +what to do when a queue is full diff --git a/doc/FEATURE b/doc/FEATURE new file mode 100644 index 000000000..af9c7a7a9 --- /dev/null +++ b/doc/FEATURE @@ -0,0 +1,3 @@ +si un server deconne, se connecter a un autre +* Gui : + calcule de idle time diff --git a/doc/HISTORY b/doc/HISTORY new file mode 100644 index 000000000..01e3bbeb8 --- /dev/null +++ b/doc/HISTORY @@ -0,0 +1,2 @@ +10/10/2003 23h39 : premier passage online +12/10/2003 02h23 : affichage de la contact list diff --git a/doc/gajimrc b/doc/gajimrc new file mode 100644 index 000000000..5907cad5d --- /dev/null +++ b/doc/gajimrc @@ -0,0 +1,9 @@ +[Server] + +hostname = SERVER HOSTNAME + +[Profile] + +name = LOGIN NAME +password = PASSWORD +ressource = gajim diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 000000000..c6905daec --- /dev/null +++ b/plugins/__init__.py @@ -0,0 +1 @@ +import gtkgui diff --git a/plugins/gtkgui.glade b/plugins/gtkgui.glade new file mode 100644 index 000000000..153c4d00b --- /dev/null +++ b/plugins/gtkgui.glade @@ -0,0 +1,404 @@ + + + + + + + True + Gajim + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 100 + 300 + True + False + + + + + True + False + 0 + + + + True + GTK_SHADOW_OUT + GTK_POS_LEFT + GTK_POS_TOP + + + + True + + + + True + _Gajim + True + + + + + + + True + + + + + + True + gtk-quit + True + + + + + + + + + + + True + _? + True + + + + + + + True + _About + True + + + + + + + + + + + + 0 + False + True + + + + + + True + GTK_SHADOW_OUT + GTK_POS_LEFT + GTK_POS_TOP + + + + True + GTK_ORIENTATION_HORIZONTAL + GTK_TOOLBAR_BOTH + True + + + + + + + + + + + + + + + + + 0 + False + True + + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_NONE + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + False + True + + + + + + + 0 + True + True + + + + + + True + True + 0 + + + + + + + True + Online + True + + + + + + + True + Away + True + + + + + + + True + NA + True + + + + + + + True + DND + True + + + + + + + True + Invisible + True + + + + + + + True + + + + + + True + Offline + True + + + + + + + + 0 + False + False + + + + + + + + True + Chat + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 400 + 300 + True + False + + + + + True + False + 0 + + + + True + False + 0 + + + + True + True + button1 + True + GTK_RELIEF_NORMAL + + + 0 + False + False + + + + + + True + True + button2 + True + GTK_RELIEF_NORMAL + + + 0 + False + False + + + + + + True + contact + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + 0 + False + True + + + + + + True + True + 190 + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + False + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + True + True + + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + GTK_JUSTIFY_LEFT + GTK_WRAP_WORD + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + True + True + + + + + 0 + True + True + + + + + + + diff --git a/plugins/gtkgui.py b/plugins/gtkgui.py new file mode 100644 index 000000000..de62d73b9 --- /dev/null +++ b/plugins/gtkgui.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python +## plugins/gtkgui.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - David Ferlier +## +## Copyright (C) 2003 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import pygtk +pygtk.require('2.0') +import gtk +import gtk.glade +import gobject + +class user: + def __init__(self, *args): + if len(args) == 0: + self.name = '' + self.group = '' + self.show = '' + self.status = '' + elif len(args) == 4: + self.name = args[0] + self.group = args[1] + self.show = args[2] + self.status = args[3] + elif ((len(args)) and (type (args[0]) == type (self)) and + (self.__class__ == args[0].__class__)): + self.name = args[0].name + self.group = args[0].group + self.show = args[0].show + self.status = args[0].status + else: raise TypeError, 'bad arguments' + +class message: + def delete_event(self, widget): + del self.roster.tab_messages[self.jid] + self.window.destroy() + + def print_conversation(self, txt, contact = None): + txt_buffer = self.conversation.get_buffer() + end_iter = txt_buffer.get_end_iter() + if contact: who = 'moi' + else: who = 'lui' + txt_buffer.insert(end_iter, '<'+who+'> '+txt+'\n', -1) + + def on_msg_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Return: + if (event.state & gtk.gdk.SHIFT_MASK): + return 0 + txt_buffer = widget.get_buffer() + start_iter = txt_buffer.get_start_iter() + end_iter = txt_buffer.get_end_iter() + txt = txt_buffer.get_text(start_iter, end_iter, 0) + self.roster.queueOUT.put(('MSG',(self.jid, txt))) + txt_buffer.set_text('', -1) + self.print_conversation(txt, self.jid) + widget.grab_focus() + return 1 + return 0 + + def __init__(self, jid, roster): + self.jid = jid + self.roster = roster + self.xml = gtk.glade.XML('plugins/gtkgui.glade', 'Chat') + self.window = self.xml.get_widget('Chat') + self.window.set_title('Chat with ' + jid) + self.message = self.xml.get_widget('message') + self.conversation = self.xml.get_widget('conversation') + self.window.show() + self.xml.signal_connect('gtk_widget_destroy', self.delete_event) + self.xml.signal_connect('on_msg_key_press_event', self.on_msg_key_press_event) + +class roster: + def get_icon_pixbuf(self, stock): + return self.tree.render_icon(stock, size = gtk.ICON_SIZE_MENU, detail = None) + + def mkl_group(self): + self.l_group = [] + for u in self.l_contact: + if u.group in self.l_group: + pass + else: + self.l_group.append(u.group) + + def mkroster(self): + self.treestore.clear() + for g in self.l_group: + iter_g = self.treestore.append(None, (self.pixbufs['online'], g, 'group')) + for c in self.l_contact: + if c.group == g: +# print c.status + self.treestore.append(iter_g, (self.pixbufs[c.show], c.name, c.show)) +# if c.status == 'Online': +# self.treestore.append(iter_g, (self.pixbufs['Online'], c.name, 'Online')) +# elif c.status == None: +# self.treestore.append(iter_g, (self.pixbufs['away'], c.name, 'away')) + + def mkroster2(self, tab): + self.l_contact = [] + for jid in tab.keys(): + user1 = user(jid, 'general', tab[jid]["Show"], tab[jid]["Status"]) + self.l_contact.append(user1) + self.mkl_group() + self.mkroster() + self.tree.collapse_row((0,3)) + + def update_iter(self, widget, path, iter, data): + val = self.treestore.get_value(iter, 1) + if val == data[0]: + self.treestore.set_value(iter, 0, self.pixbufs[data[1]]) + + def chg_status(self, jid, show, status): + for u in self.l_contact: + if u.name == jid: + u.show = show + u.status = status + self.treestore.foreach(self.update_iter, (jid, show)) + + def mk_menu_c(self, event): + self.menu_c = gtk.Menu() + item = gtk.MenuItem("user1") + self.menu_c.append(item) + item = gtk.MenuItem("user2") + self.menu_c.append(item) + item = gtk.MenuItem("user3") + self.menu_c.append(item) + self.menu_c.popup(None, None, None, event.button, event.time) + self.menu_c.show_all() + + def mk_menu_g(self, event): + self.menu_c = gtk.Menu() + item = gtk.MenuItem("grp1") + self.menu_c.append(item) + item = gtk.MenuItem("grp2") + self.menu_c.append(item) + item = gtk.MenuItem("grp3") + self.menu_c.append(item) + self.menu_c.popup(None, None, None, event.button, event.time) + self.menu_c.show_all() + + def on_treeview_event(self, widget, event): + if (event.button == 3) & (event.type == gtk.gdk.BUTTON_PRESS): + try: + path, column, x, y = self.tree.get_path_at_pos(int(event.x), int(event.y)) + + except TypeError: + return + iter = self.treestore.get_iter(path) + data = self.treestore.get_value(iter, 2) + if data == 'group': + self.mk_menu_g(event) + else: + self.mk_menu_c(event) + return gtk.TRUE + return gtk.FALSE + + def on_status_changed(self, widget): + self.queueOUT.put(('STATUS',widget.name)) + + def on_quit(self, widget): + self.queueOUT.put(('QUIT','')) + gtk.mainquit() + + def on_row_activated(self, widget, path, col): + iter = self.treestore.get_iter(path) + jid = self.treestore.get_value(iter, 1) + if self.tab_messages.has_key(jid): + #NE FONCTIONNE PAS ! + self.tab_messages[jid].window.grab_focus() + else: + self.tab_messages[jid]=message(jid, self) + + def __init__(self, queueOUT): + #initialisation des variables + # FIXME : handle no file ... + self.xml = gtk.glade.XML('plugins/gtkgui.glade', 'Gajim') + self.tree = self.xml.get_widget('treeview') + self.treestore = gtk.TreeStore(gtk.gdk.Pixbuf, str, str) + add_pixbuf = self.get_icon_pixbuf(gtk.STOCK_ADD) + remove_pixbuf = self.get_icon_pixbuf(gtk.STOCK_REMOVE) + self.pixbufs = {"online":add_pixbuf, "away":remove_pixbuf, "xa":remove_pixbuf, "dnd":remove_pixbuf, "offline":remove_pixbuf} + self.tree.set_model(self.treestore) + self.queueOUT = queueOUT + self.optionmenu = self.xml.get_widget('optionmenu') + self.optionmenu.set_history(6) + self.tab_messages = {} + #colonnes + self.col = gtk.TreeViewColumn() + render_pixbuf = gtk.CellRendererPixbuf() + self.col.pack_start(render_pixbuf, expand = False) + self.col.add_attribute(render_pixbuf, 'pixbuf', 0) + render_text = gtk.CellRendererText() + self.col.pack_start(render_text, expand = True) + self.col.add_attribute(render_text, 'text', 1) + self.tree.append_column(self.col) + #signales + self.xml.signal_connect('gtk_main_quit', self.on_quit) + self.xml.signal_connect('on_quit_activate', self.on_quit) + self.xml.signal_connect('on_treeview_event', self.on_treeview_event) + self.xml.signal_connect('on_status_changed', self.on_status_changed) + self.xml.signal_connect('on_row_activated', self.on_row_activated) +# self.mk_menu_c() + +class plugin: + def read_queue(self): + while self.queueIN.empty() == 0: + ev = self.queueIN.get() + print ev + if ev[0] == 'ROSTER': + self.r.mkroster2(ev[1]) + elif ev[0] == 'NOTIFY': + self.r.chg_status(ev[1][0], ev[1][1], ev[1][2]) + elif ev[0] == 'MSG': + if not self.r.tab_messages.has_key(ev[1][0]): + self.r.tab_messages[ev[1][0]] = message(ev[1][0], self.r) + self.r.tab_messages[ev[1][0]].print_conversation(ev[1][1]) + return 1 + + def __init__(self, quIN, quOUT): + gtk.threads_init() + gtk.threads_enter() + self.queueIN = quIN + self.r = roster(quOUT) + self.time = gtk.timeout_add(200, self.read_queue) + gtk.main() + gtk.threads_leave() + +if __name__ == "__main__": + plugin(None, None) + +print "plugin gui loaded" diff --git a/runCore.py b/runCore.py new file mode 100644 index 000000000..143948793 --- /dev/null +++ b/runCore.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +## runCore.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - David Ferlier +## +## Copyright (C) 2003 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import logging +logging.basicConfig() +import sys + +sys.path.append("..") + +import common +import core + +core.core.start()