Mercurial > libervia-backend
diff src/sat.tac @ 331:0a8eb0461f31
core: main SAT class now moved in its own module core.sat_main
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 23 May 2011 21:32:28 +0200 |
parents | 608a4a2ba94e |
children | cf005701624b |
line wrap: on
line diff
--- a/src/sat.tac Mon May 23 21:18:58 2011 +0200 +++ b/src/sat.tac Mon May 23 21:32:28 2011 +0200 @@ -19,606 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. """ -CONST = { - 'client_name' : u'SàT (Salut à toi)', - 'client_version' : u'0.1.1D', #Please add 'D' at the end for dev versions - 'local_dir' : '~/.sat' -} - import gettext gettext.install('sat', "i18n", unicode=True) -from twisted.application import internet, service -from twisted.internet import glib2reactor, protocol, task, defer +from twisted.application import service +from twisted.internet import glib2reactor glib2reactor.install() -from twisted.words.protocols.jabber import jid, xmlstream -from twisted.words.protocols.jabber import error as jab_error -from twisted.words.xish import domish - -from twisted.internet import reactor -import pdb - -from wokkel import client, disco, xmppim, generic, compat - -from sat.bridge.DBus import DBusBridge -import logging -from logging import debug, info, error - -import signal, sys -import os.path - -from sat.core.xmpp import SatXMPPClient, SatMessageProtocol, SatRosterProtocol, SatPresenceProtocol, SatDiscoProtocol, SatFallbackHandler, RegisteringAuthenticator, SatVersionHandler -from sat.tools.memory import Memory -from sat.tools.xml_tools import tupleList2dataForm -from sat.tools.misc import TriggerManager -from glob import glob - -try: - from twisted.words.protocols.xmlstream import XMPPHandler -except ImportError: - from wokkel.subprotocols import XMPPHandler - - -### logging configuration FIXME: put this elsewhere ### -logging.basicConfig(level=logging.DEBUG, - format='%(message)s') -### - - -sat_id = 0 - -def sat_next_id(): - global sat_id - sat_id+=1 - return "sat_id_"+str(sat_id) - -class SAT(service.Service): - - def get_next_id(self): - return sat_next_id() - - def get_const(self, name): - """Return a constant""" - if not CONST.has_key(name): - error(_('Trying to access an undefined constant')) - raise Exception - return CONST[name] - - def set_const(self, name, value): - """Save a constant""" - if CONST.has_key(name): - error(_('Trying to redefine a constant')) - raise Exception - CONST[name] = value - - def __init__(self): - #TODO: standardize callback system - - local_dir = os.path.expanduser(self.get_const('local_dir')) - if not os.path.exists(local_dir): - os.makedirs(local_dir) - - self.__waiting_conf = {} #callback called when a confirmation is received - self.__progress_cb_map = {} #callback called when a progress is requested (key = progress id) - self.__general_cb_map = {} #callback called for general reasons (key = name) - self.__private_data = {} #used for internal callbacks (key = id) - self.trigger = TriggerManager() #trigger are user to change SàT behaviour - self.profiles = {} - self.plugins = {} - self.menus = {} #used to know which new menus are wanted by plugins - - self.memory=Memory(self) - - self.bridge=DBusBridge() - self.bridge.register("getVersion", lambda: self.get_const('client_version')) - self.bridge.register("getProfileName", self.memory.getProfileName) - self.bridge.register("getProfilesList", self.memory.getProfilesList) - self.bridge.register("createProfile", self.memory.createProfile) - self.bridge.register("deleteProfile", self.memory.deleteProfile) - self.bridge.register("registerNewAccount", self.registerNewAccount) - self.bridge.register("connect", self.connect) - self.bridge.register("disconnect", self.disconnect) - self.bridge.register("getContacts", self.memory.getContacts) - self.bridge.register("getPresenceStatus", self.memory.getPresenceStatus) - self.bridge.register("getWaitingSub", self.memory.getWaitingSub) - self.bridge.register("sendMessage", self.sendMessage) - self.bridge.register("setParam", self.setParam) - self.bridge.register("getParamA", self.memory.getParamA) - self.bridge.register("getParamsUI", self.memory.getParamsUI) - self.bridge.register("getParams", self.memory.getParams) - self.bridge.register("getParamsForCategory", self.memory.getParamsForCategory) - self.bridge.register("getParamsCategories", self.memory.getParamsCategories) - self.bridge.register("getHistory", self.memory.getHistory) - self.bridge.register("setPresence", self.setPresence) - self.bridge.register("subscription", self.subscription) - self.bridge.register("addContact", self.addContact) - self.bridge.register("delContact", self.delContact) - self.bridge.register("isConnected", self.isConnected) - self.bridge.register("launchAction", self.launchAction) - self.bridge.register("confirmationAnswer", self.confirmationAnswer) - self.bridge.register("getProgress", self.getProgress) - self.bridge.register("getMenus", self.getMenus) - self.bridge.register("getMenuHelp", self.getMenuHelp) - self.bridge.register("callMenu", self.callMenu) - - self._import_plugins() - - - def _import_plugins(self): - """Import all plugins found in plugins directory""" - import sat.plugins - plugins_path = os.path.dirname(sat.plugins.__file__) - plug_lst = [os.path.splitext(plugin)[0] for plugin in map(os.path.basename,glob (os.path.join(plugins_path,"plugin*.py")))] - __plugins_to_import = {} #plugins will still have to import - for plug in plug_lst: - plugin_path = 'sat.plugins.'+plug - __import__(plugin_path) - mod = sys.modules[plugin_path] - plugin_info = mod.PLUGIN_INFO - __plugins_to_import[plugin_info['import_name']] = (plugin_path, mod, plugin_info) - while True: - self._import_plugins_from_dict(__plugins_to_import) - if not __plugins_to_import: - break - - def _import_plugins_from_dict(self, plugins_to_import, import_name=None): - """Recursively import and their dependencies in the right order - @param plugins_to_import: dict where key=import_name and values= (plugin_path, module, plugin_info)""" - if self.plugins.has_key(import_name): - debug('Plugin [%s] already imported, passing' % import_name) - return - if not import_name: - import_name,(plugin_path, mod, plugin_info) = plugins_to_import.popitem() - else: - if not import_name in plugins_to_import: - raise ImportError(_('Dependency plugin not found: [%s]') % import_name) - plugin_path, mod, plugin_info = plugins_to_import.pop(import_name) - dependencies = plugin_info.setdefault("dependencies",[]) - for dependency in dependencies: - if not self.plugins.has_key(dependency): - debug('Recursively import dependency of [%s]: [%s]' % (import_name, dependency)) - self._import_plugins_from_dict(plugins_to_import, dependency) - info (_("importing plugin: %s"), plugin_info['name']) - self.plugins[import_name] = getattr(mod, plugin_info['main'])(self) - if plugin_info.has_key('handler') and plugin_info['handler'] == 'yes': - self.plugins[import_name].is_handler = True - else: - self.plugins[import_name].is_handler = False - #TODO: test xmppclient presence and register handler parent - - def connect(self, profile_key = '@DEFAULT@'): - """Connect to jabber server""" - - profile = self.memory.getProfileName(profile_key) - if not profile: - error (_('Trying to connect a non-exsitant profile')) - return - - if (self.isConnected(profile)): - info(_("already connected !")) - return - current = self.profiles[profile] = SatXMPPClient(self, profile, - jid.JID(self.memory.getParamA("JabberID", "Connection", profile_key = profile), profile), - self.memory.getParamA("Password", "Connection", profile_key = profile), - self.memory.getParamA("Server", "Connection", profile_key = profile), 5222) - - current.messageProt = SatMessageProtocol(self) - current.messageProt.setHandlerParent(current) - - current.roster = SatRosterProtocol(self) - current.roster.setHandlerParent(current) - - current.presence = SatPresenceProtocol(self) - current.presence.setHandlerParent(current) - - current.fallBack = SatFallbackHandler(self) - current.fallBack.setHandlerParent(current) - - current.versionHandler = SatVersionHandler(self.get_const('client_name'), - self.get_const('client_version')) - current.versionHandler.setHandlerParent(current) - - debug (_("setting plugins parents")) - - for plugin in self.plugins.iteritems(): - if plugin[1].is_handler: - plugin[1].getHandler(profile).setHandlerParent(current) - - current.startService() - - def disconnect(self, profile_key='@DEFAULT@'): - """disconnect from jabber server""" - if (not self.isConnected(profile_key)): - info(_("not connected !")) - return - profile = self.memory.getProfileName(profile_key) - info(_("Disconnecting...")) - self.profiles[profile].stopService() - - def startService(self): - info("Salut à toi ô mon frère !") - #TODO: manage autoconnect - #self.connect() - - def stopService(self): - self.memory.save() - info("Salut aussi à Rantanplan") - - def run(self): - debug(_("running app")) - reactor.run() - - def stop(self): - debug(_("stopping app")) - reactor.stop() - - ## Misc methods ## - - def getJidNStream(self, profile_key): - """Convenient method to get jid and stream from profile key - @return: tuple (jid, xmlstream) from profile, can be None""" - profile = self.memory.getProfileName(profile_key) - if not profile or not self.profiles[profile].isConnected(): - return (None, None) - return (self.profiles[profile].jid, self.profiles[profile].xmlstream) - - def getClient(self, profile_key): - """Convenient method to get client from profile key - @return: client or None if it doesn't exist""" - profile = self.memory.getProfileName(profile_key) - if not profile: - return None - return self.profiles[profile] - - def registerNewAccount(self, login, password, server, port = 5222, id = None): - """Connect to a server and create a new account using in-band registration""" - - next_id = id or sat_next_id() #the id is used to send server's answer - serverRegistrer = xmlstream.XmlStreamFactory(RegisteringAuthenticator(self, server, login, password, next_id)) - connector = reactor.connectTCP(server, port, serverRegistrer) - serverRegistrer.clientConnectionLost = lambda conn, reason: connector.disconnect() - - return next_id - - def registerNewAccountCB(self, id, data, profile): - user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0] - password = self.memory.getParamA("Password", "Connection", profile_key=profile) - server = self.memory.getParamA("Server", "Connection", profile_key=profile) - - if not user or not password or not server: - info (_('No user or server given')) - #TODO: a proper error message must be sent to frontend - self.actionResult(id, "ERROR", {'message':_("No user, password or server given, can't register new account.")}) - return - - confirm_id = sat_next_id() - self.__private_data[confirm_id]=(id,profile) - - self.askConfirmation(confirm_id, "YES/NO", - {"message":_("Are you sure to register new account [%(user)s] to server %(server)s ?") % {'user':user, 'server':server, 'profile':profile}}, - self.regisConfirmCB) - print ("===============+++++++++++ REGISTER NEW ACCOUNT++++++++++++++============") - print "id=",id - print "data=",data - - def regisConfirmCB(self, id, accepted, data): - print _("register Confirmation CB ! (%s)") % str(accepted) - action_id,profile = self.__private_data[id] - del self.__private_data[id] - if accepted: - user = jid.parse(self.memory.getParamA("JabberID", "Connection", profile_key=profile))[0] - password = self.memory.getParamA("Password", "Connection", profile_key=profile) - server = self.memory.getParamA("Server", "Connection", profile_key=profile) - self.registerNewAccount(user, password, server, id=action_id) - else: - self.actionResult(action_id, "SUPPRESS", {}) - - def submitForm(self, action, target, fields, profile_key='@DEFAULT@'): - """submit a form - @param target: target jid where we are submitting - @param fields: list of tuples (name, value) - @return: tuple: (id, deferred) - """ - - profile = self.memory.getProfileName(profile_key) - assert(profile) - to_jid = jid.JID(target) - - iq = compat.IQ(self.profiles[profile].xmlstream, 'set') - iq["to"] = target - iq["from"] = self.profiles[profile].jid.full() - query = iq.addElement(('jabber:iq:register', 'query')) - if action=='SUBMIT': - form = tupleList2dataForm(fields) - query.addChild(form.toElement()) - elif action=='CANCEL': - query.addElement('remove') - else: - error (_("FIXME FIXME FIXME: Unmanaged action (%s) in submitForm") % action) - raise NotImplementedError - - deferred = iq.send(target) - return (iq['id'], deferred) - - ## Client management ## - - def setParam(self, name, value, category, profile_key='@DEFAULT@'): - """set wanted paramater and notice observers""" - info (_("setting param: %(name)s=%(value)s in category %(category)s") % {'name':name, 'value':value, 'category':category}) - self.memory.setParam(name, value, category, profile_key) - - def isConnected(self, profile_key='@DEFAULT@'): - """Return connection status of profile - @param profile_key: key_word or profile name to determine profile name - @return True if connected - """ - profile = self.memory.getProfileName(profile_key) - if not profile: - error (_('asking connection status for a non-existant profile')) - return - if not self.profiles.has_key(profile): - return False - return self.profiles[profile].isConnected() - - def launchAction(self, type, data, profile_key='@DEFAULT@'): - """Launch a specific action asked by client - @param type: action type (button) - @param data: needed data to launch the action - - @return: action id for result, or empty string in case or error - """ - profile = self.memory.getProfileName(profile_key) - if not profile: - error (_('trying to launch action with a non-existant profile')) - raise Exception #TODO: raise a proper exception - if type=="button": - try: - cb_name = data['callback_id'] - except KeyError: - error (_("Incomplete data")) - return "" - id = sat_next_id() - self.callGeneralCB(cb_name, id, data, profile = profile) - return id - else: - error (_("Unknown action type")) - return "" - - - ## jabber methods ## - - def sendMessage(self, to, msg, subject=None, type='chat', profile_key='@DEFAULT@'): - #FIXME: check validity of recipient - profile = self.memory.getProfileName(profile_key) - assert(profile) - current_jid = self.profiles[profile].jid - debug(_("Sending jabber message to %s..."), to) - message = domish.Element(('jabber:client','message')) - message["to"] = jid.JID(to).full() - message["from"] = current_jid.full() - message["type"] = type - if subject: - message.addElement("subject", "jabber:client", subject) - message.addElement("body", "jabber:client", msg) - self.profiles[profile].xmlstream.send(message) - self.memory.addToHistory(current_jid, current_jid, jid.JID(to), message["type"], unicode(msg)) - if type!="groupchat": - self.bridge.newMessage(message['from'], unicode(msg), mess_type=type, to_jid=message['to'], profile=profile) #We send back the message, so all clients are aware of it - - - def setPresence(self, to="", show="", priority = 0, statuses={}, profile_key='@DEFAULT@'): - """Send our presence information""" - profile = self.memory.getProfileName(profile_key) - assert(profile) - to_jid = jid.JID(to) if to else None - self.profiles[profile].presence.available(to_jid, show, statuses, priority) - #XXX: FIXME: temporary fix to work around openfire 3.7.0 bug (presence is not broadcasted to generating resource) - if statuses.has_key(''): - statuses['default'] = statuses[''] - del statuses[''] - self.bridge.presenceUpdate(self.profiles[profile].jid.full(), show, - int(priority), statuses, profile) - - - def subscription(self, subs_type, raw_jid, profile_key='@DEFAULT@'): - """Called to manage subscription - @param subs_type: subsciption type (cf RFC 3921) - @param raw_jid: unicode entity's jid - @param profile_key: profile""" - profile = self.memory.getProfileName(profile_key) - assert(profile) - to_jid = jid.JID(raw_jid) - debug (_('subsciption request [%(subs_type)s] for %(jid)s') % {'subs_type':subs_type, 'jid':to_jid.full()}) - if subs_type=="subscribe": - self.profiles[profile].presence.subscribe(to_jid) - elif subs_type=="subscribed": - self.profiles[profile].presence.subscribed(to_jid) - contact = self.memory.getContact(to_jid) - if not contact or not bool(contact['to']): #we automatically subscribe to 'to' presence - debug(_('sending automatic "to" subscription request')) - self.subscription('subscribe', to_jid.userhost()) - elif subs_type=="unsubscribe": - self.profiles[profile].presence.unsubscribe(to_jid) - elif subs_type=="unsubscribed": - self.profiles[profile].presence.unsubscribed(to_jid) - - - def addContact(self, to, profile_key='@DEFAULT@'): - """Add a contact in roster list""" - profile = self.memory.getProfileName(profile_key) - assert(profile) - to_jid=jid.JID(to) - #self.profiles[profile].roster.addItem(to_jid) XXX: disabled (cf http://wokkel.ik.nu/ticket/56)) - self.profiles[profile].presence.subscribe(to_jid) - - def delContact(self, to, profile_key='@DEFAULT@'): - """Remove contact from roster list""" - profile = self.memory.getProfileName(profile_key) - assert(profile) - to_jid=jid.JID(to) - self.profiles[profile].roster.removeItem(to_jid) - self.profiles[profile].presence.unsubscribe(to_jid) - self.bridge.contactDeleted(to, profile) - - - ## callbacks ## - - def serverDisco(self, disco, profile): - """xep-0030 Discovery Protocol.""" - for feature in disco.features: - debug (_("Feature found: %s"),feature) - self.memory.addServerFeature(feature, profile) - for cat, type in disco.identities: - debug (_("Identity found: [%(category)s/%(type)s] %(identity)s") % {'category':cat, 'type':type, 'identity':disco.identities[(cat,type)]}) - - def serverDiscoItems(self, disco_result, disco_client, profile, initialized): - """xep-0030 Discovery Protocol. - @param disco_result: result of the disco item querry - @param disco_client: SatDiscoProtocol instance - @param profile: profile of the user - @param initialized: deferred which must be chained when everything is done""" - def _check_entity_cb(result, entity, profile): - for category, type in result.identities: - debug (_('Identity added: (%(category)s,%(type)s) ==> %(entity)s [%(profile)s]') % { - 'category':category, 'type':type, 'entity':entity, 'profile':profile}) - self.memory.addServerIdentity(category, type, entity, profile) - - defer_list = [] - for item in disco_result._items: - defer_list.append(disco_client.requestInfo(item.entity).addCallback(_check_entity_cb, item.entity, profile)) - defer.DeferredList(defer_list).chainDeferred(initialized) - - - ## Generic HMI ## - - def actionResult(self, id, type, data): - """Send the result of an action - @param id: same id used with action - @param type: result type ("PARAM", "SUCCESS", "ERROR", "XMLUI") - @param data: dictionary - """ - self.bridge.actionResult(type, id, data) - - def actionResultExt(self, id, type, data): - """Send the result of an action, extended version - @param id: same id used with action - @param type: result type /!\ only "DICT_DICT" for this method - @param data: dictionary of dictionaries - """ - if type != "DICT_DICT": - error(_("type for actionResultExt must be DICT_DICT, fixing it")) - type = "DICT_DICT" - self.bridge.actionResultExt(type, id, data) - - - - def askConfirmation(self, id, type, data, cb): - """Add a confirmation callback - @param id: id used to get answer - @param type: confirmation type ("YES/NO", "FILE_TRANSFERT") - @param data: data (depend of confirmation type) - @param cb: callback called with the answer - """ - if self.__waiting_conf.has_key(id): - error (_("Attempt to register two callbacks for the same confirmation")) - else: - self.__waiting_conf[id] = cb - self.bridge.askConfirmation(type, id, data) - - - def confirmationAnswer(self, id, accepted, data): - """Called by frontends to answer confirmation requests""" - debug (_("Received confirmation answer for id [%(id)s]: %(success)s") % {'id': id, 'success':_("accepted") if accepted else _("refused")}) - if not self.__waiting_conf.has_key(id): - error (_("Received an unknown confirmation")) - else: - cb = self.__waiting_conf[id] - del self.__waiting_conf[id] - cb(id, accepted, data) - - def registerProgressCB(self, id, CB): - """Register a callback called when progress is requested for id""" - self.__progress_cb_map[id] = CB - - def removeProgressCB(self, id): - """Remove a progress callback""" - if not self.__progress_cb_map.has_key(id): - error (_("Trying to remove an unknow progress callback")) - else: - del self.__progress_cb_map[id] - - def getProgress(self, id): - """Return a dict with progress information - data['position'] : current possition - data['size'] : end_position - """ - data = {} - try: - self.__progress_cb_map[id](data) - except KeyError: - pass - #debug("Requested progress for unknown id") - return data - - def registerGeneralCB(self, name, CB): - """Register a callback called for general reason""" - self.__general_cb_map[name] = CB - - def removeGeneralCB(self, name): - """Remove a general callback""" - if not self.__general_cb_map.has_key(name): - error (_("Trying to remove an unknow general callback")) - else: - del self.__general_cb_map[name] - - def callGeneralCB(self, name, *args, **kwargs): - """Call general function back""" - try: - return self.__general_cb_map[name](*args, **kwargs) - except KeyError: - error(_("Trying to call unknown function (%s)") % name) - return None - - #Menus management - - def importMenu(self, category, name, callback, help_string = "", type = "NORMAL"): - """register a new menu for frontends - @param category: category of the menu - @param name: menu item entry - @param callback: method to be called when menuitem is selected""" - if self.menus.has_key((category,name)): - error ("Want to register a menu which already existe") - return - self.menus[(category,name,type)] = {'callback':callback, 'help_string':help_string, 'type':type} - - def getMenus(self): - """Return all menus registered""" - return self.menus.keys() - - def getMenuHelp(self, category, name, type="NORMAL"): - """return the help string of the menu""" - try: - return self.menus[(category,name,type)]['help_string'] - except KeyError: - error (_("Trying to access an unknown menu")) - return "" - - def callMenu(self, category, name, type="NORMAL", profile_key='@DEFAULT@'): - """return the id of the action""" - profile = self.memory.getProfileName(profile_key) - if not profile_key: - error (_('Non-exsitant profile')) - return "" - if self.menus.has_key((category,name,type)): - id = self.get_next_id() - self.menus[(category,name,type)]['callback'](id, profile) - return id - else: - error (_("Trying to access an unknown menu (%(category)s/%(name)s/%(type)s)")%{'category':category, 'name':name,'type':type}) - return "" - - +from sat.core.sat_main import SAT application = service.Application('SàT') service = SAT()