diff --git a/TODO b/TODO index e69de29..2c2e449 100644 --- a/TODO +++ b/TODO @@ -0,0 +1,3 @@ +* pylint sources +* make tests for every methods +* merge all necessary features from jmc to jcl diff --git a/src/jcl/__init__.py b/src/jcl/__init__.py index e69de29..2d9fb23 100644 --- a/src/jcl/__init__.py +++ b/src/jcl/__init__.py @@ -0,0 +1 @@ +__revision__ = "$Id: __init__.py dax $" diff --git a/src/jcl/jabber/component.py b/src/jcl/jabber/component.py index ddbc803..eff5f9c 100644 --- a/src/jcl/jabber/component.py +++ b/src/jcl/jabber/component.py @@ -21,13 +21,26 @@ ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## +"""JCL base component +""" + +__revision__ = "" + +import thread +import threading +import time import logging import signal +import re from pyxmpp.jid import JID from pyxmpp.jabberd.component import Component +from pyxmpp.jabber.disco import DiscoInfo, DiscoItems +from pyxmpp.message import Message from pyxmpp.presence import Presence +from pyxmpp.streambase import StreamError, FatalStreamError +from jcl.jabber.x import X from jcl.model.account import Account VERSION = "0.1" @@ -35,58 +48,128 @@ VERSION = "0.1" ############################################################################### # JCL implementation ############################################################################### -class JCLComponent(Component): +class JCLComponent(Component): + """Implement default JCL component behavior: + - regular interval behavior + - Jabber register process (add, delete, update accounts) + - Jabber presence handling + - passwork request at login + """ + + timeout = 1 + def __init__(self, jid, secret, server, port, - name = "Jabber Component Library generic component", disco_category = "gateway", - disco_type = "headline", - spool_dir = ".", - check_interval = 1, - account_class = Account): + disco_type = "headline"): Component.__init__(self, \ JID(jid), \ secret, \ + server, \ port, \ disco_category, \ disco_type) + # default values + self.name = "Jabber Component Library generic component" + self.spool_dir = "." + self.__account_class = None + self.account_class = Account + self.version = VERSION + self.accounts = [] + + self.disco_info.add_feature("jabber:iq:version") + self.disco_info.add_feature("jabber:iq:register") self.__logger = logging.getLogger("jcl.jabber.JCLComponent") # TODO : self.__lang = Lang(default_lang) - self.__name = name - self.__disco_category = disco_category - self.__disco_type = disco_type - self.__interval = check_interval + self.__lang = None self.running = False - self.__shutdown = 0 - signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler) - spool_dir += "/" + jid + + def set_account_class(self, account_class): self.__account_class = account_class self.__account_class.createTable(ifNotExists = True) - self.version = VERSION - + + def get_account_class(self): + return self.__account_class + + account_class = property(get_account_class, set_account_class) + + def run(self): + """Main loop + Connect to Jabber server + Start timer thread + Call Component main loop + Clean up when shutting down JCLcomponent + """ + self.spool_dir += "/" + str(self.jid) + self.running = True + self.connect() + thread.start_new_thread(self.time_handler, ()) + try: + while (self.running and self.stream + and not self.stream.eof and self.stream.socket is not None): + try: + self.stream.loop_iter(JCLComponent.timeout) + except (KeyboardInterrupt, SystemExit, FatalStreamError, \ + StreamError): + raise + except: + self.__logger.exception("Exception cought:") + finally: + if self.stream: + # TODO : send unavailble from transport and all account to users + pass +# for jid in self.__storage.keys(()): +# p = Presence(from_jid = unicode(self.jid), to_jid = jid, \ +# stanza_type = "unavailable") +# self.stream.send(p) +# for jid, name in self.__storage.keys(): +# if self.__storage[(jid, name)].status != "offline": +# p = Presence(from_jid = name + "@" + unicode(self.jid),\ +# to_jid = jid, \ +# stanza_type = "unavailable") +# self.stream.send(p) + threads = threading.enumerate() + for _thread in threads: + try: + _thread.join(10 * JCLComponent.timeout) + except: + pass + for _thread in threads: + try: + _thread.join(JCLComponent.timeout) + except: + pass + self.disconnect() + # TODO : terminate SQLObject + self.__logger.debug("Exitting normally") + + + + ########################################################################### + # Handlers + ########################################################################### + def time_handler(self): + """Timer thread handler + """ + self.__logger.info("Timer thread started...") + while self.running: + self.handle_tick() + self.__logger.debug("Resetting alarm signal") + time.sleep(60) + def authenticated(self): - Component.authenticated(self) + """Override authenticated Component event handler + Register event handlers + Probe for every accounts registered + """ self.__logger.debug("AUTHENTICATED") - # Send probe for transport - current_jid = None - for account in self.__account_class.select(orderBy = "user_jid"): - if account.user_jid != current_jid: - p = Presence(from_jid = unicode(self.jid), \ - to_jid = account.user_jid, \ - stanza_type = "probe") - self.stream.send(p) - current_jid = account.user_jid - p = Presence(from_jid = self.get_jid(account), \ - to_jid = account.user_jid, \ - stanza_type = "probe") - self.stream.send(p) - + Component.authenticated(self) self.stream.set_iq_get_handler("query", "jabber:iq:version", \ self.handle_get_version) self.stream.set_iq_get_handler("query", "jabber:iq:register", \ @@ -114,180 +197,128 @@ class JCLComponent(Component): self.stream.set_message_handler("normal", \ self.handle_message) + current_jid = None + for account in self.account_class.select(orderBy = "user_jid"): + if account.user_jid != current_jid: + presence = Presence(from_jid = unicode(self.jid), \ + to_jid = account.user_jid, \ + stanza_type = "probe") + self.stream.send(presence) + current_jid = account.user_jid + presence = Presence(from_jid = self.get_jid(account), \ + to_jid = account.user_jid, \ + stanza_type = "probe") + self.stream.send(presence) - def run(self): - self.connect() - self.running = True - thread.start_new_thread(self.time_handler, ()) - try: - while (not self.__shutdown and self.stream - and not self.stream.eof and self.stream.socket is not None): - try: - self.stream.loop_iter(timeout) - except (KeyboardInterrupt, SystemExit, FatalStreamError, \ - StreamError): - raise - except: - self.__logger.exception("Exception cought:") - finally: - self.running = False - if self.stream: - # TODO : send unavailble from transport and all account to users - pass -# for jid in self.__storage.keys(()): -# p = Presence(from_jid = unicode(self.jid), to_jid = jid, \ -# stanza_type = "unavailable") -# self.stream.send(p) -# for jid, name in self.__storage.keys(): -# if self.__storage[(jid, name)].status != "offline": -# p = Presence(from_jid = name + "@" + unicode(self.jid), \ -# to_jid = jid, \ -# stanza_type = "unavailable") -# self.stream.send(p) - threads = threading.enumerate() - for th in threads: - try: - th.join(10 * timeout) - except: - pass - for th in threads: - try: - th.join(timeout) - except: - pass - self.disconnect() - # TODO : terminate SQLObject - self.__logger.debug("Exitting normally") - - def get_reg_form(self, lang_class, account_class): - pass - - def get_reg_form_init(self, lang_class, account): - pass - - def _ask_password(self, lang_class, account): - if not account.waiting_password_reply \ - and account.status != "offline": - account.waiting_password_reply = True - msg = Message(from_jid = account.jid, \ - to_jid = from_jid, \ - stanza_type = "normal", \ - subject = u"[PASSWORD] " + lang_class.ask_password_subject, \ - body = lang_class.ask_password_body % \ - (account.host, account.login)) - self.stream.send(msg) - - def get_jid(self, account): - return account.name + u"@" + unicode(self.jid) - - ## Handlers - - """ Stop method handler """ def signal_handler(self, signum, frame): + """Stop method handler + """ self.__logger.debug("Signal %i received, shutting down..." % (signum,)) - self.__shutdown = 1 + self.running = False - - def stream_state_changed(self,state,arg): - self.__logger.debug("*** State changed: %s %r ***" % (state,arg)) - - """ Discovery get info handler """ - def disco_get_info(self, node, iq): + def disco_get_info(self, node, input_query): + """Discovery get info handler + """ self.__logger.debug("DISCO_GET_INFO") - di = DiscoInfo() - if node is None: - di.add_feature("jabber:iq:version") - di.add_feature("jabber:iq:register") - DiscoIdentity(di, self.__name, \ - self.__disco_category, \ - self.__disco_type) + if node is not None: + disco_info = DiscoInfo() + disco_info.add_feature("jabber:iq:register") + return disco_info else: - di.add_feature("jabber:iq:register") - return di + return self.disco_info - """ Discovery get nested nodes handler """ - def disco_get_items(self, node, iq): + def disco_get_items(self, node, input_query): + """Discovery get nested nodes handler + """ self.__logger.debug("DISCO_GET_ITEMS") -# lang_class = self.__lang.get_lang_class_from_node(iq.get_node()) - base_from_jid = unicode(iq.get_from().bare()) - di = DiscoItems() +## TODO Lang +## lang_class = self.__lang.get_lang_class_from_node(input_query.get_node()) +## base_from_jid = unicode(input_query.get_from().bare()) + disco_items = DiscoItems() if not node: - # TODO : list accounts - for account in self.__accounts: - pass -# DiscoItem(di, JID(name + "@" + unicode(self.jid)), \ -# name, str_name) - return di +## TODO : list accounts + for account in self.accounts: + self.__logger.debug(str(account)) +## DiscoItem(di, JID(name + "@" + unicode(self.jid)), \ +## name, str_name) + return disco_items - """ Get Version handler """ - def handle_get_version(self, iq): + def handle_get_version(self, input_query): + """Get Version handler + """ self.__logger.debug("GET_VERSION") - iq = iq.make_result_response() - q = iq.new_query("jabber:iq:version") - q.newTextChild(q.ns(), "name", self.__name) - q.newTextChild(q.ns(), "version", self.version) - self.stream.send(iq) + input_query = input_query.make_result_response() + query = input_query.new_query("jabber:iq:version") + query.newTextChild(query.ns(), "name", self.name) + query.newTextChild(query.ns(), "version", self.version) + self.stream.send(input_query) return 1 - """ Send back register form to user """ - def handle_get_register(self, iq): + def handle_get_register(self, input_query): + """Send back register form to user + """ self.__logger.debug("GET_REGISTER") -# lang_class = self.__lang.get_lang_class_from_node(iq.get_node()) - base_from_jid = unicode(iq.get_from().bare()) - to = iq.get_to() - iq = iq.make_result_response() - q = iq.new_query("jabber:iq:register") - if to and to != self.jid: +## TODO Lang +## lang_class = self.__lang.get_lang_class_from_node(input_query.get_node()) + lang_class = None +## base_from_jid = unicode(input_query.get_from().bare()) + to_jid = input_query.get_to() + input_query = input_query.make_result_response() + query = input_query.new_query("jabber:iq:register") + if to_jid and to_jid != self.jid: self.get_reg_form_init(lang_class, \ - self.__accounts.select() # TODO - ).attach_xml(q) + self.accounts.select() # TODO + ).attach_xml(query) else: - self.get_reg_form(lang_class).attach_xml(q) - self.stream.send(iq) + self.get_reg_form(lang_class).attach_xml(query) + self.stream.send(input_query) return 1 - """ Handle user registration response """ - def handle_set_register(self, iq): + def handle_set_register(self, input_query): + """Handle user registration response + """ self.__logger.debug("SET_REGISTER") - lang_class = self.__lang.get_lang_class_from_node(iq.get_node()) - to = iq.get_to() - from_jid = iq.get_from() - base_from_jid = unicode(from_jid.bare()) - remove = iq.xpath_eval("r:query/r:remove", \ - {"r" : "jabber:iq:register"}) +## lang_class = \ +## self.__lang.get_lang_class_from_node(input_query.get_node()) + from_jid = input_query.get_from() +## base_from_jid = unicode(from_jid.bare()) + remove = input_query.xpath_eval("r:query/r:remove", \ + {"r" : "jabber:iq:register"}) if remove: - for name in self.__storage.keys((base_from_jid,)): - self.__logger.debug("Deleting " + name + " for " + base_from_jid) - p = Presence(from_jid = name + "@" + unicode(self.jid), \ - to_jid = from_jid, \ - stanza_type = "unsubscribe") - self.stream.send(p) - p = Presence(from_jid = name + "@" + unicode(self.jid), \ - to_jid = from_jid, \ - stanza_type = "unsubscribed") - self.stream.send(p) - del self.__storage[(base_from_jid, name)] - p = Presence(from_jid = self.jid, to_jid = from_jid, \ - stanza_type = "unsubscribe") - self.stream.send(p) - p = Presence(from_jid = self.jid, to_jid = from_jid, \ - stanza_type = "unsubscribed") - self.stream.send(p) +# for name in self.__storage.keys((base_from_jid,)): +# self.__logger.debug("Deleting " + name \ +# + " for " + base_from_jid) +# presence = Presence(from_jid = name + "@" + unicode(self.jid), \ +# to_jid = from_jid, \ +# stanza_type = "unsubscribe") +# self.stream.send(presence) +# presence = Presence(from_jid = name + "@" + unicode(self.jid), \ +# to_jid = from_jid, \ +# stanza_type = "unsubscribed") +# self.stream.send(presence) +# del self.__storage[(base_from_jid, name)] + presence = Presence(from_jid = self.jid, to_jid = from_jid, \ + stanza_type = "unsubscribe") + self.stream.send(presence) + presence = Presence(from_jid = self.jid, to_jid = from_jid, \ + stanza_type = "unsubscribed") + self.stream.send(presence) return 1 - query = iq.get_query() - x = X() - x.from_xml(query.children) + query = input_query.get_query() + x_data = X() + x_data.from_xml(query.children) # TODO : get info from Xdata - """ Handle presence availability """ def handle_presence_available(self, stanza): + """Handle presence availability + """ self.__logger.debug("PRESENCE_AVAILABLE") from_jid = stanza.get_from() base_from_jid = unicode(from_jid.bare()) name = stanza.get_to().node - lang_class = self.__lang.get_lang_class_from_node(stanza.get_node()) +## lang_class = self.__lang.get_lang_class_from_node(stanza.get_node()) show = stanza.get_show() self.__logger.debug("SHOW : " + str(show)) if name: @@ -296,63 +327,71 @@ class JCLComponent(Component): # else send available presence return 1 - """ handle presence unavailability """ def handle_presence_unavailable(self, stanza): + """Handle presence unavailability + """ self.__logger.debug("PRESENCE_UNAVAILABLE") - from_jid = stanza.get_from() - base_from_jid = unicode(from_jid.bare()) +## from_jid = stanza.get_from() +## base_from_jid = unicode(from_jid.bare()) # TODO : send unavailable to all user's account if target is transport # else send unavailable back return 1 - """ handle subscribe presence from user """ def handle_presence_subscribe(self, stanza): + """Handle subscribe presence from user + """ self.__logger.debug("PRESENCE_SUBSCRIBE") - p = stanza.make_accept_response() - self.stream.send(p) + presence = stanza.make_accept_response() + self.stream.send(presence) return 1 - """ handle subscribed presence from user """ def handle_presence_subscribed(self, stanza): + """Handle subscribed presence from user + """ self.__logger.debug("PRESENCE_SUBSCRIBED") name = stanza.get_to().node - from_jid = stanza.get_from() - base_from_jid = unicode(from_jid.bare()) +## from_jid = stanza.get_from() +## base_from_jid = unicode(from_jid.bare()) # TODO : send presence available to subscribed user return 1 - """ handle unsubscribe presence from user """ def handle_presence_unsubscribe(self, stanza): + """Handle unsubscribe presence from user + """ self.__logger.debug("PRESENCE_UNSUBSCRIBE") name = stanza.get_to().node from_jid = stanza.get_from() - base_from_jid = unicode(from_jid.bare()) +## base_from_jid = unicode(from_jid.bare()) # TODO : delete from account base - p = Presence(from_jid = stanza.get_to(), to_jid = from_jid, \ - stanza_type = "unsubscribe") - self.stream.send(p) - p = stanza.make_accept_response() - self.stream.send(p) + presence = Presence(from_jid = stanza.get_to(), to_jid = from_jid, \ + stanza_type = "unsubscribe") + self.stream.send(presence) + presence = stanza.make_accept_response() + self.stream.send(presence) return 1 - """ handle unsubscribed presence from user """ def handle_presence_unsubscribed(self, stanza): + """Handle unsubscribed presence from user + """ self.__logger.debug("PRESENCE_UNSUBSCRIBED") - p = Presence(from_jid = stanza.get_to(), \ - to_jid = stanza.get_from(), \ - stanza_type = "unavailable") - self.stream.send(p) + presence = Presence(from_jid = stanza.get_to(), \ + to_jid = stanza.get_from(), \ + stanza_type = "unavailable") + self.stream.send(presence) return 1 - """ Handle new message """ def handle_message(self, message): + """Handle new message + """ self.__logger.debug("MESSAGE: " + message.get_body()) lang_class = self.__lang.get_lang_class_from_node(message.get_node()) - name = message.get_to().node - base_from_jid = unicode(message.get_from().bare()) - if re.compile("\[PASSWORD\]").search(message.get_subject()) is not None: -# TODO and self.__storage.has_key((base_from_jid, name)): -# account = self.__storage[(base_from_jid, name)] +## name = message.get_to().node +## base_from_jid = unicode(message.get_from().bare()) + if re.compile("\[PASSWORD\]").search(message.get_subject()) \ + is not None: +## TODO and self.__storage.has_key((base_from_jid, name)): +## account = self.__storage[(base_from_jid, name)] + account = Account() account.password = message.get_body() account.waiting_password_reply = False msg = Message(from_jid = account.jid, \ @@ -363,5 +402,47 @@ class JCLComponent(Component): self.stream.send(msg) return 1 + ########################################################################### + # Utils + ########################################################################### + def _ask_password(self, from_jid, lang_class, account): + """Send a Jabber message to ask for account password + """ + #TODO be JMC independant + if not account.waiting_password_reply \ + and account.status != "offline": + account.waiting_password_reply = True + msg = Message(from_jid = account.jid, \ + to_jid = from_jid, \ + stanza_type = "normal", \ + subject = u"[PASSWORD] " + \ + lang_class.ask_password_subject, \ + body = lang_class.ask_password_body % \ + (account.host, account.login)) + self.stream.send(msg) + + def get_jid(self, account): + """Return account jid based on account instance and component jid + """ + return account.name + u"@" + unicode(self.jid) + + ########################################################################### + # Virtual methods + ########################################################################### def handle_tick(self): + """Virtual method + Called regularly + """ + pass + + def get_reg_form(self, lang_class, account_class): + """Virtual method + Return register form based on language and account class + """ + pass + + def get_reg_form_init(self, lang_class, account): + """Virtual method + Return register form for an existing account (update) + """ pass diff --git a/src/jcl/jabber/feeder.py b/src/jcl/jabber/feeder.py index f48ab48..75216fe 100644 --- a/src/jcl/jabber/feeder.py +++ b/src/jcl/jabber/feeder.py @@ -21,50 +21,70 @@ ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## +"""FeederComponent with default Feeder and Sender +implementation +""" + +__revision__ = "$Id: feeder.py dax $" + +import logging + from jcl.jabber.component import JCLComponent -from jcl.model.account import * +from jcl.model.account import Account class FeederComponent(JCLComponent): + """Implement a feeder sender behavior based on the + regular interval behavior of JCLComponent + feed data from given Feeder and send it to user + through the given Sender. + """ def __init__(self, jid, secret, server, - port, - name = "Generic Feeder Component", - disco_category = "gateway", - disco_type = "headline", - spool_dir = ".", - check_interval = 1): + port): JCLComponent.__init__(self, \ jid, \ secret, \ server, \ - port, \ - name, \ - disco_category, \ - disco_type, \ - spool_dir, \ - check_interval) + port) + self.name = "Generic Feeder Component" + # Define default feeder and sender, can be override + self.feeder = Feeder() + self.sender = Sender() + self.check_interval = 1 + + self.__logger = logging.getLogger("jcl.jabber.JCLComponent") def handle_tick(self): + """Implement main feed/send behavior + """ for account in Account.select("*"): - for message in self.__jabber_component.feeder.feed(account): - self.__jabber_component.sender.send(account, message) + for data in self.feeder.feed(account): + self.sender.send(account, data) class Feeder(object): + """Abstract feeder class + """ def __init__(self): pass def feed(self, account): + """Feed data for given account + """ pass class Sender(object): + """Abstract sender class + """ def __init__(self): pass - def send(self, to_account, message): + def send(self, to_account, data): + """Send data to given account + """ pass diff --git a/src/jcl/jabber/x.py b/src/jcl/jabber/x.py index a9e41d3..a2201ea 100644 --- a/src/jcl/jabber/x.py +++ b/src/jcl/jabber/x.py @@ -20,110 +20,134 @@ ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## -import sys +"""X -- X data handling +""" + +__revision__ = "" + from pyxmpp.stanza import common_doc class Option(object): + """Option value for list field + """ def __init__(self, label, value): - self.__label = label - self.__value = value - + self.__label = label + self.__value = value + def get_xml(self, parent): - if parent is None: - option = common_doc.newChild(None, "option", None) - else: - option = parent.newChild(None, "option", None) - option.setProp("label", self.__label) - option.newChild(None, "value", self.__value) - return option + """Return XML Option representation from + self.__label and self.__value and attach it to parent + """ + if parent is None: + option = common_doc.newChild(None, "option", None) + else: + option = parent.newChild(None, "option", None) + option.setProp("label", self.__label) + option.newChild(None, "value", self.__value) + return option class Field(object): - def __init__(self, type, label, var, value): - self.__type = type - self.__label = label - self.__var = var - self.value = value - self.__options = [] + """Jabber Xdata form Field + """ + def __init__(self, field_type, label, var, value): + self.__type = field_type + self.__label = label + self.__var = var + self.value = value + self.__options = [] def add_option(self, label, value): - option = Option(label, value) - self.__options.append(option) - return option + """Add an Option to this field + """ + option = Option(label, value) + self.__options.append(option) + return option def get_xml(self, parent): - if parent is None: - raise Exception, "parent field should not be None" - else: - field = parent.newChild(None, "field", None) - field.setProp("type", self.__type) - if not self.__label is None: - field.setProp("label", self.__label) - if not self.__var is None: - field.setProp("var", self.__var) - if self.value: - field.newChild(None, "value", self.value) - for option in self.__options: - option.get_xml(field) - return field + """Return XML Field representation + and attach it to parent + """ + if parent is None: + raise Exception, "parent field should not be None" + else: + field = parent.newChild(None, "field", None) + field.setProp("type", self.__type) + if not self.__label is None: + field.setProp("label", self.__label) + if not self.__var is None: + field.setProp("var", self.__var) + if self.value: + field.newChild(None, "value", self.value) + for option in self.__options: + option.get_xml(field) + return field class X(object): + """Jabber Xdata form + """ def __init__(self): - self.fields = {} - self.fields_tab = [] - self.title = None - self.instructions = None - self.type = None + self.fields = {} + self.fields_tab = [] + self.title = None + self.instructions = None + self.x_type = None self.xmlns = None - def add_field(self, type = "fixed", label = None, var = None, value = ""): - field = Field(type, label, var, value) - self.fields[var] = field - # fields_tab exist to keep added fields order - self.fields_tab.append(field) - return field + def add_field(self, field_type = "fixed", label = None, var = None, value = ""): + """Add a Field to this Xdata form + """ + field = Field(field_type, label, var, value) + self.fields[var] = field + # fields_tab exist to keep added fields order + self.fields_tab.append(field) + return field def attach_xml(self, iq): - node = iq.newChild(None, "x", None) - _ns = node.newNs(self.xmlns, None) - node.setNs(_ns) - if not self.title is None: - node.newTextChild(None, "title", self.title) - if not self.instructions is None: - node.newTextChild(None, "instructions", self.instructions) - for field in self.fields_tab: - field.get_xml(node) - return node + """Attach this Xdata form to iq node + """ + node = iq.newChild(None, "x", None) + _ns = node.newNs(self.xmlns, None) + node.setNs(_ns) + if not self.title is None: + node.newTextChild(None, "title", self.title) + if not self.instructions is None: + node.newTextChild(None, "instructions", self.instructions) + for field in self.fields_tab: + field.get_xml(node) + return node def from_xml(self, node): - ## TODO : test node type and ns and clean that loop !!!! - while node and node.type != "element": - node = node.next - child = node.children - while child: - ## TODO : test child type (element) and ns (jabber:x:data) - if child.type == "element" and child.name == "field": - if child.hasProp("type"): - type = child.prop("type") - else: - type = "" + """Populate this X object from an XML representation + """ +## TODO : test node type and ns and clean that loop !!!! + while node and node.type != "element": + node = node.next + child = node.children + while child: +## TODO : test child type (element) and ns (jabber:x:data) + if child.type == "element" and child.name == "field": + if child.hasProp("type"): + field_type = child.prop("type") + else: + field_type = "" - if child.hasProp("label"): - label = child.prop("label") - else: - label = "" + if child.hasProp("label"): + label = child.prop("label") + else: + label = "" - if child.hasProp("var"): - var = child.prop("var") - else: - var = "" - - xval = child.children - while xval and xval.name != "value": - xval = xval.next - if xval: - value = xval.getContent() - else: - value = "" - field = Field(type, label, var, value) - self.fields[var] = field - child = child.next + if child.hasProp("var"): + var = child.prop("var") + else: + var = "" + + xval = child.children + while xval and xval.name != "value": + xval = xval.next + if xval: + value = xval.getContent() + else: + value = "" + field = Field(field_type, label, var, value) + self.fields[var] = field + child = child.next diff --git a/src/jcl/lang.py b/src/jcl/lang.py index 5aeb025..0617a2c 100644 --- a/src/jcl/lang.py +++ b/src/jcl/lang.py @@ -21,12 +21,27 @@ ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## -# TODO delete not JCL translation +"""lang -- contains translations +""" + +# TODO get help to generate revision +__revision__ = "$Id: lang.py dax $" + +# TODO delete JMC translation class Lang: + """Lang. + """ + # TODO get help on docstring + # pylint: disable-msg=W0232, R0903, C0103, C0111 def __init__(self, default_lang = "en"): self.default_lang = default_lang def get_lang_from_node(self, node): + """Extract lang contain in a XML node. + + :Parameters: + - `node`: XML node. + """ lang = node.getLang() if lang is None: print "Using default lang " + self.default_lang @@ -34,9 +49,19 @@ class Lang: return lang def get_lang_class(self, lang): - return getattr(Lang, lang) + """Return lang class from lang code. + + :Parameters: + - `lang`: lang code. + """ + return getattr(self, lang) def get_lang_class_from_node(self, node): + """Return lang class from XML node. + + :Parameters: + - `node`: XML node. + """ return self.get_lang_class(self.get_lang_from_node(node)) class en: @@ -57,7 +82,8 @@ class Lang: account_dnd_action = u"Action when state is 'Do not Disturb'" account_offline_action = u"Action when state is 'Offline'" account_check_interval = u"Mail check interval (in minutes)" - account_live_email_only = u"Reports only emails received while connected to Jabber" + account_live_email_only = u"Reports only emails received while " \ + u"connected to Jabber" action_nothing = u"Do nothing" action_retrieve = u"Retrieve mail" action_digest = u"Send mail digest" @@ -66,7 +92,7 @@ class Lang: connection_label = u"%s connection '%s'" update_account_message_subject = u"Updated %s connection '%s'" update_account_message_body = u"Registered with username '%s' and " \ - "password '%s' on '%s'" + u"password '%s' on '%s'" new_account_message_subject = u"New %s connection '%s' created" new_account_message_body = u"Registered with " \ "username '%s' and password '%s' on '%s'" @@ -75,19 +101,22 @@ class Lang: "for the following account: \n" \ "\thost = %s\n" \ "\tlogin = %s\n" - password_saved_for_session = u"Password will be kept during your Jabber session" + password_saved_for_session = u"Password will be kept during your " \ + u"Jabber session" check_error_subject = u"Error while checking emails." check_error_body = u"An error appears while checking emails:\n\t%s" new_mail_subject = u"New email from %s" new_digest_subject = u"%i new email(s)" class fr: - register_title = u"Enregistrement d'une nouvelle connexion à un serveur email." + register_title = u"Enregistrement d'une nouvelle connexion à un " \ + u"serveur email." register_instructions = u"Entrer les paramètres de connexion" account_name = u"Nom de la connexion" account_login = u"Nom d'utilisateur" account_password = u"Mot de passe" - account_password_store = u"Sauvegarder le mot de passe sur le serveur Jabber ?" + account_password_store = u"Sauvegarder le mot de passe sur le " \ + u"serveur Jabber ?" account_host = u"Adresse du serveur email" account_port = u"Port du serveur email" account_type = u"Type du serveur email" @@ -98,7 +127,8 @@ class Lang: account_xa_action = u"Action lorsque l'état est 'Not Available'" account_dnd_action = u"Action lorsque l'état est 'Do not Disturb'" account_offline_action = u"Action lorsque l'état est 'Offline'" - account_check_interval = u"Interval de vérification de nouveaux emails (en minutes)" + account_check_interval = u"Interval de vérification de nouveaux " \ + u"emails (en minutes)" account_live_email_only = u"Vérifier les nouveaux emails seulement " \ "lorsqu'une session Jabber est ouverte" action_nothing = u"Ne rien faire" @@ -107,20 +137,23 @@ class Lang: update_title = u"Mise à jour du compte JMC" update_instructions = u"Modification de la connexion '%s'" connection_label = u"Connexion %s '%s'" - update_account_message_subject = u"La connexion %s '%s' a été mise à jour" - update_account_message_body = u"Nom d'utilisateur : '%s'\nMot de passe : '%s'\nsur : '%s'" + update_account_message_subject = u"La connexion %s '%s' a été mise " \ + u"à jour" + update_account_message_body = u"Nom d'utilisateur : '%s'\nMot de " \ + u"passe : '%s'\nsur : '%s'" new_account_message_subject = u"La connexion %s '%s' a été créée" - new_account_message_body = u"Nom d'utilisateur : '%s'\nMot de passe : '%s'\nsur : '%s'" + new_account_message_body = u"Nom d'utilisateur : '%s'\nMot de passe " \ + u": '%s'\nsur : '%s'" ask_password_subject = u"Demande de mot de passe" - ask_password_body = u"Répondre à ce message avec le mot de passe du " \ - "compte suivant : \n" \ - "\thost = %s\n" \ - "\tlogin = %s\n" + ask_password_body = u"Répondre à ce message avec le mot de passe " \ + u"du compte suivant : \n" \ + u"\thost = %s\n" \ + u"\tlogin = %s\n" password_saved_for_session = u"Le mot de passe sera garder tout au " \ - "long de la session Jabber." + u"long de la session Jabber." check_error_subject = u"Erreur lors de la vérification des emails." - check_error_body = u"Une erreur est survenue lors de la vérification " \ - "des emails :\n\t%s" + check_error_body = u"Une erreur est survenue lors de la " \ + u"vérification des emails :\n\t%s" new_mail_subject = u"Nouvel email de %s" new_digest_subject = u"%i nouveau(x) email(s)" @@ -152,16 +185,18 @@ class Lang: connection_label = u"%s verbinding '%s'" update_account_message_subject = u"Verbinding %s '%s' werd bijgewerkt" update_account_message_body = u"Geregistreerd met gebruikersnaam '%s'"\ - "en wachtwoord '%s' op '%s'" + u"en wachtwoord '%s' op '%s'" new_account_message_subject = u"Nieuwe %s verbinding '%s' aangemaakt" new_account_message_body = u"Geregistreerd met " \ - "gebruikersnaam '%s' en wachtwoord '%s' op '%s'" + u"gebruikersnaam '%s' en wachtwoord " \ + u"'%s' op '%s'" ask_password_subject = u"Wachtwoordaanvraag" - ask_password_body = u"Antwoord dit bericht met het volgende wachtwoord" \ - "voor de volgende account: \n" \ - "\thost = %s\n" \ - "\tlogin = %s\n" - password_saved_for_session = u"Het wachtwoord zal worden bewaard tijdens uw Jabber-sessie" + ask_password_body = u"Antwoord dit bericht met het volgende " \ + u"wachtwoord voor de volgende account: \n" \ + u"\thost = %s\n" \ + u"\tlogin = %s\n" + password_saved_for_session = u"Het wachtwoord zal worden bewaard " \ + u"tijdens uw Jabber-sessie" check_error_subject = u"Fout tijdens controle op e-mails." check_error_body = u"Fout tijdens controle op e-mails:\n\t%s" new_mail_subject = u"Nieuwe e-mail van %s" @@ -173,19 +208,24 @@ class Lang: account_name = u"Nombre para la cuenta" account_login = u"Usuario (login)" account_password = u"Contraseña" - account_password_store = u"¿Guardar la contraseña en el servidor Jabber?" + account_password_store = u"¿Guardar la contraseña en el servidor " \ + u"Jabber?" account_host = u"Host" account_port = u"Puerto" account_type = u"Tipo de servidor Mail" account_mailbox = u"Ruta del mailbox (solo para IMAP)" - account_ffc_action = u"Acción para cuando tu estado sea 'Listopara hablar'" + account_ffc_action = u"Acción para cuando tu estado sea " \ + u"'Listopara hablar'" account_online_action = u"Acción para cuando tu estado sea 'Conectado'" account_away_action = u"Acción para cuando tu estado sea 'Ausente'" account_xa_action = u"Acción para cuando tu estado sea 'No disponible'" account_dnd_action = u"Acción para cuando tu estado sea 'No molestar'" - account_offline_action = u"Acción para cuando tu estado sea 'Desconectado'" - account_check_interval = u"Intervalo para comprobar emails nuevos (en minutos)" - account_live_email_only = u"Avisarme de emails nuevos solo cuando esté conectado" + account_offline_action = u"Acción para cuando tu estado sea " \ + u"'Desconectado'" + account_check_interval = u"Intervalo para comprobar emails nuevos " \ + u"(en minutos)" + account_live_email_only = u"Avisarme de emails nuevos solo cuando " \ + u"esté conectado" action_nothing = u"No hacer nada" action_retrieve = u"Mostrarme el email" action_digest = u"Enviar resúmen" @@ -193,15 +233,19 @@ class Lang: update_instructions = u"Modifica los datos de la cuenta '%s'" connection_label = u"%s conexión '%s'" update_account_message_subject = u"Actualizada %s conexión '%s'" - update_account_message_body = u"Registrado con el usuario '%s' y contraseña '%s' en '%s'" + update_account_message_body = u"Registrado con el usuario '%s' y " \ + u"contraseña '%s' en '%s'" new_account_message_subject = u"Nueva %s conexión '%s' creada" - new_account_message_body = u"Registrado con usuario '%s' y contraseña '%s' en '%s'" + new_account_message_body = u"Registrado con usuario '%s' y " \ + u"contraseña '%s' en '%s'" ask_password_subject = u"Petición de contraseña" - ask_password_body = u"Para avisarte de emails nuevos, contesta a este mensaje con la contraseña " \ - "de la cuenta: \n" \ - "\tHost = %s\n" \ - "\tUsuario = %s\n" - password_saved_for_session = u"La contraseña será guardada para esta sesión únicamente." + ask_password_body = u"Para avisarte de emails nuevos, contesta a " \ + u"este mensaje con la contraseña " \ + u"de la cuenta: \n" \ + u"\tHost = %s\n" \ + u"\tUsuario = %s\n" + password_saved_for_session = u"La contraseña será guardada para " \ + u"esta sesión únicamente." check_error_subject = u"Error al revisar los emails." check_error_body = u"Un error apareció al revisar los emails:\n\t%s" new_mail_subject = u"Nuevo email en %s" @@ -225,7 +269,8 @@ class Lang: account_dnd_action = u"Akcja gdy status to 'Nie przeszkadzać'" account_offline_action = u"Akcja gdy status to 'Rozłączony'" account_check_interval = u"Sprawdzaj email co (w minutach)" - account_live_email_only = u"Raportuj otrzymane emaile tylko\n gdy podłączony do Jabbera" + account_live_email_only = u"Raportuj otrzymane emaile tylko\n gdy " \ + u"podłączony do Jabbera" action_nothing = u"Nic nie rób" action_retrieve = u"Pobierz emaila" action_digest = u"Wyślij zarys emaila" @@ -233,15 +278,20 @@ class Lang: update_instructions = u"Modyfikacja połączenia '%s'" connection_label = u"%s połączenie '%s'" update_account_message_subject = u"Zmodyfikowane %s połączenie '%s'" - update_account_message_body = u"Zarejestrowany z nazwą użytkownika '%s' i hasłem '%s' na '%s'" + update_account_message_body = u"Zarejestrowany z nazwą użytkownika " \ + u"'%s' i hasłem '%s' na '%s'" new_account_message_subject = u"Nowe %s połączenie '%s' utworzone" - new_account_message_body = u"Zarejestrowany z nazwą użytkownika '%s' i hasłem '%s' na '%s'" + new_account_message_body = u"Zarejestrowany z nazwą użytkownika " \ + u"'%s' i hasłem '%s' na '%s'" ask_password_subject = u"Żądanie hasła" - ask_password_body = u"Odpowiedz na ta wiadomosc z hasłem dla podanego konta: \n" \ - "\tnazwa hosta = %s\n" \ - "\tnazwa uzytkownika = %s\n" - password_saved_for_session = u"Hasło będzie przechowywane podczas Twojej sesji Jabbera" + ask_password_body = u"Odpowiedz na ta wiadomosc z hasłem dla " \ + u"podanego konta: \n" \ + u"\tnazwa hosta = %s\n" \ + u"\tnazwa uzytkownika = %s\n" + password_saved_for_session = u"Hasło będzie przechowywane podczas " \ + u"Twojej sesji Jabbera" check_error_subject = u"Błąd podczas sprawdzania emaili." - check_error_body = u"Pojawił się błąd podczas sprawdzania emaili:\n\t%s" + check_error_body = u"Pojawił się błąd podczas sprawdzania " \ + u"emaili:\n\t%s" new_mail_subject = u"Nowy email od %s" new_digest_subject = u"%i nowy(ch) email(i)" diff --git a/src/jcl/model/account.py b/src/jcl/model/account.py index 02953c5..a857a26 100644 --- a/src/jcl/model/account.py +++ b/src/jcl/model/account.py @@ -26,5 +26,7 @@ from sqlobject import * class Account(SQLObject): user_jid = StringCol() name = StringCol() + jid = StringCol() + diff --git a/tests/jcl/jabber/test_component.py b/tests/jcl/jabber/test_component.py index 0caa8f5..a4400e0 100644 --- a/tests/jcl/jabber/test_component.py +++ b/tests/jcl/jabber/test_component.py @@ -22,6 +22,11 @@ ## import unittest + +import thread +import threading +import time + from sqlobject import * from jcl.jabber.component import JCLComponent @@ -31,6 +36,8 @@ from jcl.lang import Lang class MockStream(object): def __init__(self): self.sended = [] + self.connection_started = False + self.connection_stoped = False def send(self, iq): self.sended.append(iq) @@ -65,6 +72,15 @@ class MockStream(object): raise Exception("Message type unknown: " + msg_type) if handler is None: raise Exception("Handler must not be None") + + def connect(self): + self.connection_started = True + + def disconnect(self): + self.connection_stoped = True + + def loop_iter(self, timeout): + time.sleep(timeout) class JCLComponent_TestCase(unittest.TestCase): def setUp(self): @@ -80,6 +96,25 @@ class JCLComponent_TestCase(unittest.TestCase): def test_constructor(self): self.assertTrue(Account._connection.tableExists("account")) + def test_run(self): + self.comp.stream = MockStream() + run_thread = thread.start_new_thread(self.comp.run, ()) + self.assertTrue(self.comp.stream.connection_started) + self.comp.running = False + time.sleep(JCLComponent.timeout + 1) + threads = threading.enumerate() + self.assertNone(threads) + for _thread in threads: + try: + _thread.join(1) + except: + pass + self.assertTrue(self.comp.connection_stoped) + + def test_run_go_offline(self): + ## TODO : verify offline stanza are sent + pass + def test_authenticated_handler(self): self.comp.stream = MockStream() self.comp.authenticated()