changeset 224:9c6ee3f9ab29

files reorganisation
author Goffi <goffi@goffi.org>
date Tue, 04 Jan 2011 19:30:27 +0100
parents 86d249b6d9b7
children fd9b7834d98a
files src/__init__.py src/bridge/DBus.py src/bridge/__init__.py src/bridge/bridge.py src/sat/__init__.py src/sat/bridge/DBus.py src/sat/bridge/__init__.py src/sat/bridge/bridge.py src/sat/tools/__init__.py src/sat/tools/games.py src/sat/tools/jid.py src/sat/tools/memory.py src/sat/tools/xml_tools.py src/tools/__init__.py src/tools/games.py src/tools/jid.py src/tools/memory.py src/tools/xml_tools.py
diffstat 12 files changed, 1557 insertions(+), 1557 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/DBus.py	Tue Jan 04 19:30:27 2011 +0100
@@ -0,0 +1,401 @@
+#!/usr/bin/python
+#-*- coding: utf-8 -*-
+
+"""
+SAT: a jabber client
+Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
+
+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, either version 3 of the License, 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+
+from bridge import Bridge
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+import pdb
+from logging import debug, info, error
+
+const_INT_PREFIX = "org.goffi.SAT"  #Interface prefix
+const_COMM_SUFFIX = ".communication"
+const_REQ_SUFFIX = ".request"
+
+class DbusObject(dbus.service.Object):
+
+    def __init__(self, bus, path):
+        dbus.service.Object.__init__(self, bus, path)
+        debug("Init DbusObject...")
+        self.cb={}
+
+    def register(self, name, cb):
+        self.cb[name]=cb
+
+    ### signals ###    
+
+    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
+                         signature='s')
+    def connected(self, profile):
+        debug("Connected signal")
+    
+    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
+                         signature='s')
+    def disconnected(self, profile):
+        debug("Disconnected signal")
+    
+    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
+                         signature='sa{ss}ass')
+    def newContact(self, contact, attributes, groups, profile):
+        debug("new contact signal (%s) sended (profile: %s)", contact, profile)
+
+    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
+                         signature='sssss')
+    def newMessage(self, from_jid, msg, type, to, profile):
+        debug("new message signal (from:%s msg:%s type:%s to:%s) sended", from_jid, msg, type, to)
+     
+    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
+                         signature='ssss')
+    def newAlert(self, msg, title, type, profile):
+        debug("new alert signal (title:%s type:%s msg:%s profile:%s) sended", type, title, msg, profile)
+     
+    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
+                         signature='ssia{ss}s')
+    def presenceUpdate(self, entity, show, priority, statuses, profile):
+        debug("presence update signal (from:%s show:%s priority:%d statuses:%s profile:%s) sended" , entity, show, priority, statuses, profile)
+
+    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
+                         signature='sss')
+    def subscribe(self, type, entity, profile):
+        debug("subscribe (type: [%s] from:[%s] profile:[%s])" , type, entity, profile)
+    
+    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
+                         signature='ssss')
+    def paramUpdate(self, name, value, category, profile):
+        debug("param update signal: %s=%s in category %s (profile: %s)", name, value, category, profile)
+
+    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
+                         signature='ss')
+    def contactDeleted(self, entity, profile):
+        debug("contact deleted signal: %s (profile: %s)", entity, profile)
+    
+    @dbus.service.signal(const_INT_PREFIX+const_REQ_SUFFIX,
+                         signature='ssa{ss}')
+    def askConfirmation(self, type, id, data):
+        debug("asking for confirmation: id = [%s]  type = %s data = %s", id, type, data)
+
+    @dbus.service.signal(const_INT_PREFIX+const_REQ_SUFFIX,
+                         signature='ssa{ss}')
+    def actionResult(self, type, id, data):
+        debug("result of action: id = [%s]  type = %s data = %s", id, type, data)
+
+    @dbus.service.signal(const_INT_PREFIX+const_REQ_SUFFIX,
+                         signature='ssa{sa{ss}}')
+    def actionResultExt(self, type, id, data):
+        debug("extended result of action: id = [%s]  type = %s data = %s", id, type, data)
+    
+    @dbus.service.signal(const_INT_PREFIX+const_REQ_SUFFIX,
+                         signature='sa{ss}')
+    def updatedValue(self, name, value):
+        debug("updated value: %s = %s", name, value)
+
+    ### methods ###    
+
+    
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='', out_signature='s')
+    def getVersion(self):
+        return self.cb["getVersion"]()
+    
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='s', out_signature='s')
+    def getProfileName(self, profile_key):
+        return self.cb["getProfileName"](profile_key)
+    
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='', out_signature='as')
+    def getProfilesList(self):
+        info ('Profile list asked')
+        return self.cb["getProfilesList"]()
+
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='s', out_signature='i')
+    def createProfile(self, name):
+        info ('Profile creation asked')
+        return self.cb["createProfile"](unicode(name))
+
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='s', out_signature='i')
+    def deleteProfile(self, name):
+        info ('Profile deletion asked')
+        return self.cb["deleteProfile"](str(name))
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='sssi', out_signature='s')
+    def registerNewAccount(self, login, password, host, port=5222):
+        info ("New account registration asked")
+        return self.cb["registerNewAccount"](login, password, host, port)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='s', out_signature='')
+    def connect(self, profile_key='@DEFAULT@'):
+        info ("Connection asked")
+        return self.cb["connect"](profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='s', out_signature='')
+    def disconnect(self, profile_key='@DEFAULT@'):
+        info ("Disconnection asked")
+        return self.cb["disconnect"](profile_key)
+    
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='', out_signature='b')
+    def isConnected(self, profile_key='@DEFAULT@'):
+        info ("Connection status asked")
+        return self.cb["isConnected"](profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='s', out_signature='a(sa{ss}as)')
+    def getContacts(self, profile_key='@DEFAULT@'):
+        debug("getContacts...")
+        return self.cb["getContacts"](profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='s', out_signature='a{sa{s(sia{ss})}}')
+    def getPresenceStatus(self, profile_key='@DEFAULT@'):
+        debug("getPresenceStatus...")
+        return self.cb["getPresenceStatus"](profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='s', out_signature='a{ss}')
+    def getWaitingSub(self, profile_key='@DEFAULT@'):
+        debug("getWaitingSub...")
+        return self.cb["getWaitingSub"](profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='ssss', out_signature='')
+    def sendMessage(self, to, message, type='chat', profile_key='@DEFAULT@'):
+        debug("sendMessage...")
+        print "sendtype=", type  #gof
+        self.cb["sendMessage"](to, message, type, profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='ssia{ss}s', out_signature='')
+    def setPresence(self, to="", show="", priority=0, statuses={}, profile_key='@DEFAULT@'):
+        self.cb["setPresence"](to, show, priority, statuses, profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='sss', out_signature='')
+    def subscription(self, type, entity, profile_key='@DEFAULT@'):
+        self.cb["subscription"](type, entity, profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='ssss', out_signature='')
+    def setParam(self, name, value, category, profile_key='@DEFAULT@'):
+        self.cb["setParam"](unicode(name), unicode(value), unicode(category), profile_key)
+        
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='sss', out_signature='s')
+    def getParamA(self, name, category="default", profile_key='@DEFAULT@'):
+        return self.cb["getParamA"](name, category, profile_key = profile_key)
+
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='s', out_signature='s')
+    def getParamsUI(self, profile_key='@DEFAULT@'):
+        return self.cb["getParamsUI"](profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='s', out_signature='s')
+    def getParams(self, profile_key='@DEFAULT@'):
+        return self.cb["getParams"](profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='ss', out_signature='s')
+    def getParamsForCategory(self, category, profile_key='@DEFAULT@'):
+        return self.cb["getParamsForCategory"](category, profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='', out_signature='as')
+    def getParamsCategories(self):
+        return self.cb["getParamsCategories"]()
+
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='ssi', out_signature='a{i(ss)}')
+    def getHistory(self, from_jid, to_jid, size):
+        debug("History asked for %s", to_jid)
+        return self.cb["getHistory"](from_jid, to_jid, size)
+    
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='ss', out_signature='')
+    def addContact(self, entity, profile_key='@DEFAULT@'):
+        debug("Subscription asked for %s (profile %s)", entity, profile_key)
+        return self.cb["addContact"](entity, profile_key)
+    
+    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
+                         in_signature='ss', out_signature='')
+    def delContact(self, entity, profile_key='@DEFAULT@'):
+        debug("Unsubscription asked for %s (profile %s)", entity, profile_key)
+        return self.cb["delContact"](entity, profile_key)
+
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='sa{ss}s', out_signature='s')
+    def launchAction(self, type, data, profile_key='@DEFAULT@'):
+        return self.cb["launchAction"](type, data, profile_key)
+    
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='sba{ss}', out_signature='')
+    def confirmationAnswer(self, id, accepted, data):
+        debug("Answer for confirmation [%s]: %s", id, "Accepted" if accepted else "Refused")
+        return self.cb["confirmationAnswer"](id, accepted, data)
+    
+
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='s', out_signature='a{ss}')
+    def getProgress(self, id):
+        #debug("Progress asked for %s", id)
+        return self.cb["getProgress"](id)
+
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='', out_signature='a(sss)')
+    def getMenus(self):
+        return self.cb["getMenus"]()
+    
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='sss', out_signature='s')
+    def getMenuHelp(self, category, name, type="NORMAL"):
+        return self.cb["getMenuHelp"](category, name, type)
+    
+    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
+                         in_signature='ssss', out_signature='s')
+    def callMenu(self, category, name, type, profile_key):
+        return self.cb["callMenu"](category, name, type, profile_key)
+    
+    def __attribute_string(self, in_sign):
+        i=0
+        idx=0
+        attr_string=""
+        while i<len(in_sign):
+            if in_sign[i] not in ['b','y','n','i','x','q','u','t','d','s','a']:
+                raise Exception  #FIXME: create an exception here (unmanaged attribute type)
+
+            attr_string += ("" if idx==0 else ",") + ("arg_%i" % idx)
+            idx+=1
+
+            if in_sign[i] == 'a':
+                i+=1
+                if in_sign[i]!='{' and in_sign[i]!='(': #FIXME: must manage tuples out of arrays
+                    i+=1
+                    continue #we have a simple type for the array
+                while (True): #we have a dict or a list of tuples
+                    i+=1
+                    if i>=len(in_sign):
+                        raise Exception  #FIXME: create an exception here (the '}' is not presend)
+                    if in_sign[i] == '}' or in_sign[i] == ')':
+                        break
+            i+=1
+        return attr_string
+
+
+
+    def addMethod(self, name, int_suffix, in_sign, out_sign):
+        """Dynamically add a method to Dbus Bridge"""
+        #FIXME: Better way ???
+        attributes = self.__attribute_string(in_sign)
+
+        code = compile ('def '+name+' (self,'+attributes+'): return self.cb["'+name+'"]('+attributes+')', '<DBus bridge>','exec')
+        exec (code)
+        method = locals()[name]
+        setattr(DbusObject, name, dbus.service.method(
+            const_INT_PREFIX+int_suffix, in_signature=in_sign, out_signature=out_sign)(method))
+    
+    def addSignal(self, name, int_suffix, signature):
+        """Dynamically add a signal to Dbus Bridge"""
+        #FIXME: Better way ???
+        attributes = self.__attribute_string(signature)
+
+        code = compile ('def '+name+' (self,'+attributes+'): debug ("'+name+' signal")', '<DBus bridge>','exec')
+        exec (code)
+        signal = locals()[name]
+        setattr(DbusObject, name, dbus.service.signal(
+            const_INT_PREFIX+int_suffix, signature=signature)(signal))
+
+class DBusBridge(Bridge):
+    def __init__(self):
+        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+        Bridge.__init__(self)
+        info ("Init DBus...")
+        self.session_bus = dbus.SessionBus()
+        self.dbus_name = dbus.service.BusName(const_INT_PREFIX, self.session_bus)
+        self.dbus_bridge = DbusObject(self.session_bus, '/org/goffi/SAT/bridge')
+
+    def connected(self, profile):
+        self.dbus_bridge.connected(profile)
+    
+    def disconnected(self, profile):
+        self.dbus_bridge.disconnected(profile)
+    
+    def newContact(self, contact, attributes, groups, profile):
+        self.dbus_bridge.newContact(contact, attributes, groups, profile)
+
+    def newMessage(self, from_jid, msg, type='chat', to='', profile='@NONE@'):
+        debug("sending message...")
+        self.dbus_bridge.newMessage(from_jid, msg, type, to, profile)
+
+    def newAlert(self, msg, title="", alert_type="INFO", profile='@NONE@'):
+        self.dbus_bridge.newAlert(msg, title, alert_type, profile)
+    
+    def presenceUpdate(self, entity, show, priority, statuses, profile):
+        debug("updating presence for %s",entity)
+        self.dbus_bridge.presenceUpdate(entity, show, priority, statuses, profile)
+    
+    def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile):
+        self.dbus_bridge.roomJoined(room_id, room_service, room_nicks, user_nick, profile)
+
+    def subscribe(self, sub_type, entity, profile):
+        debug("subscribe request for %s",entity)
+        self.dbus_bridge.subscribe(sub_type, entity, profile)
+
+    def paramUpdate(self, name, value, category, profile):
+        debug("updating param [%s] %s ", category, name)
+        self.dbus_bridge.paramUpdate(name, value, category, profile)
+
+    def contactDeleted(self, entity, profile):
+        debug("sending contact deleted signal %s ", entity)
+        self.dbus_bridge.contactDeleted(entity, profile)
+
+    def askConfirmation(self, type, id, data):
+        self.dbus_bridge.askConfirmation(type, id, data)
+
+    def actionResult(self, type, id, data):
+        self.dbus_bridge.actionResult(type, id, data)
+
+    def actionResultExt(self, type, id, data):
+        self.dbus_bridge.actionResultExt(type, id, data)
+
+    def updatedValue(self, name, value):
+        self.dbus_bridge.updatedValue(name, value)
+
+    def register(self, name, callback):
+        debug("registering DBus bridge method [%s]", name)
+        self.dbus_bridge.register(name, callback)
+
+    def addMethod(self, name, int_suffix, in_sign, out_sign, method):
+        """Dynamically add a method to Dbus Bridge"""
+        print ("Adding method [%s] to DBus bridge" % name)
+        self.dbus_bridge.addMethod(name, int_suffix, in_sign, out_sign)
+        self.register(name, method)
+
+    def addSignal(self, name, int_suffix, signature):
+        self.dbus_bridge.addSignal(name, int_suffix, signature)
+        setattr(DBusBridge, name, getattr(self.dbus_bridge, name))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/bridge/bridge.py	Tue Jan 04 19:30:27 2011 +0100
@@ -0,0 +1,68 @@
+#!/usr/bin/python
+#-*- coding: utf-8 -*-
+
+"""
+SAT: a jabber client
+Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
+
+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, either version 3 of the License, 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from logging import debug, info, error
+
+class Bridge:
+    def __init__(self):
+        info ("Bridge initialization")
+
+    ##signals
+    def newContact(self, contact): 
+        raise NotImplementedError
+
+    def newMessage(self, from_jid, msg, type='chat'):
+        raise NotImplementedError
+
+    def presenceUpdate(self, type, jid, show, status, priority):
+        raise NotImplementedError
+
+    def paramUpdate(self, name, value):
+        raise NotImplementedError
+
+
+    ##methods
+    def connect(self):
+        raise NotImplementedError
+
+    def getContacts(self):
+        raise NotImplementedError
+    
+    def getPresenceStatus(self):
+        raise NotImplementedError
+
+    def sendMessage(self):
+        raise NotImplementedError
+
+    def setPresence(self, to="", type="", show="", status="", priority=0):
+        raise NotImplementedError
+
+    def setParam(self, name, value, namespace):
+        raise NotImplementedError
+        
+    def getParam(self, name, namespace):
+        raise NotImplementedError
+
+    def getParams(self, namespace):
+        raise NotImplementedError
+    
+    def getHistory(self, from_jid, to_jid, size):
+        raise NotImplementedError
--- a/src/sat/bridge/DBus.py	Wed Dec 29 01:06:29 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,401 +0,0 @@
-#!/usr/bin/python
-#-*- coding: utf-8 -*-
-
-"""
-SAT: a jabber client
-Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
-
-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, either version 3 of the License, 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 General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-
-from bridge import Bridge
-import dbus
-import dbus.service
-import dbus.mainloop.glib
-import pdb
-from logging import debug, info, error
-
-const_INT_PREFIX = "org.goffi.SAT"  #Interface prefix
-const_COMM_SUFFIX = ".communication"
-const_REQ_SUFFIX = ".request"
-
-class DbusObject(dbus.service.Object):
-
-    def __init__(self, bus, path):
-        dbus.service.Object.__init__(self, bus, path)
-        debug("Init DbusObject...")
-        self.cb={}
-
-    def register(self, name, cb):
-        self.cb[name]=cb
-
-    ### signals ###    
-
-    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
-                         signature='s')
-    def connected(self, profile):
-        debug("Connected signal")
-    
-    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
-                         signature='s')
-    def disconnected(self, profile):
-        debug("Disconnected signal")
-    
-    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
-                         signature='sa{ss}ass')
-    def newContact(self, contact, attributes, groups, profile):
-        debug("new contact signal (%s) sended (profile: %s)", contact, profile)
-
-    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
-                         signature='sssss')
-    def newMessage(self, from_jid, msg, type, to, profile):
-        debug("new message signal (from:%s msg:%s type:%s to:%s) sended", from_jid, msg, type, to)
-     
-    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
-                         signature='ssss')
-    def newAlert(self, msg, title, type, profile):
-        debug("new alert signal (title:%s type:%s msg:%s profile:%s) sended", type, title, msg, profile)
-     
-    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
-                         signature='ssia{ss}s')
-    def presenceUpdate(self, entity, show, priority, statuses, profile):
-        debug("presence update signal (from:%s show:%s priority:%d statuses:%s profile:%s) sended" , entity, show, priority, statuses, profile)
-
-    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
-                         signature='sss')
-    def subscribe(self, type, entity, profile):
-        debug("subscribe (type: [%s] from:[%s] profile:[%s])" , type, entity, profile)
-    
-    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
-                         signature='ssss')
-    def paramUpdate(self, name, value, category, profile):
-        debug("param update signal: %s=%s in category %s (profile: %s)", name, value, category, profile)
-
-    @dbus.service.signal(const_INT_PREFIX+const_COMM_SUFFIX,
-                         signature='ss')
-    def contactDeleted(self, entity, profile):
-        debug("contact deleted signal: %s (profile: %s)", entity, profile)
-    
-    @dbus.service.signal(const_INT_PREFIX+const_REQ_SUFFIX,
-                         signature='ssa{ss}')
-    def askConfirmation(self, type, id, data):
-        debug("asking for confirmation: id = [%s]  type = %s data = %s", id, type, data)
-
-    @dbus.service.signal(const_INT_PREFIX+const_REQ_SUFFIX,
-                         signature='ssa{ss}')
-    def actionResult(self, type, id, data):
-        debug("result of action: id = [%s]  type = %s data = %s", id, type, data)
-
-    @dbus.service.signal(const_INT_PREFIX+const_REQ_SUFFIX,
-                         signature='ssa{sa{ss}}')
-    def actionResultExt(self, type, id, data):
-        debug("extended result of action: id = [%s]  type = %s data = %s", id, type, data)
-    
-    @dbus.service.signal(const_INT_PREFIX+const_REQ_SUFFIX,
-                         signature='sa{ss}')
-    def updatedValue(self, name, value):
-        debug("updated value: %s = %s", name, value)
-
-    ### methods ###    
-
-    
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='', out_signature='s')
-    def getVersion(self):
-        return self.cb["getVersion"]()
-    
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='s', out_signature='s')
-    def getProfileName(self, profile_key):
-        return self.cb["getProfileName"](profile_key)
-    
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='', out_signature='as')
-    def getProfilesList(self):
-        info ('Profile list asked')
-        return self.cb["getProfilesList"]()
-
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='s', out_signature='i')
-    def createProfile(self, name):
-        info ('Profile creation asked')
-        return self.cb["createProfile"](unicode(name))
-
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='s', out_signature='i')
-    def deleteProfile(self, name):
-        info ('Profile deletion asked')
-        return self.cb["deleteProfile"](str(name))
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='sssi', out_signature='s')
-    def registerNewAccount(self, login, password, host, port=5222):
-        info ("New account registration asked")
-        return self.cb["registerNewAccount"](login, password, host, port)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='s', out_signature='')
-    def connect(self, profile_key='@DEFAULT@'):
-        info ("Connection asked")
-        return self.cb["connect"](profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='s', out_signature='')
-    def disconnect(self, profile_key='@DEFAULT@'):
-        info ("Disconnection asked")
-        return self.cb["disconnect"](profile_key)
-    
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='', out_signature='b')
-    def isConnected(self, profile_key='@DEFAULT@'):
-        info ("Connection status asked")
-        return self.cb["isConnected"](profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='s', out_signature='a(sa{ss}as)')
-    def getContacts(self, profile_key='@DEFAULT@'):
-        debug("getContacts...")
-        return self.cb["getContacts"](profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='s', out_signature='a{sa{s(sia{ss})}}')
-    def getPresenceStatus(self, profile_key='@DEFAULT@'):
-        debug("getPresenceStatus...")
-        return self.cb["getPresenceStatus"](profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='s', out_signature='a{ss}')
-    def getWaitingSub(self, profile_key='@DEFAULT@'):
-        debug("getWaitingSub...")
-        return self.cb["getWaitingSub"](profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='ssss', out_signature='')
-    def sendMessage(self, to, message, type='chat', profile_key='@DEFAULT@'):
-        debug("sendMessage...")
-        print "sendtype=", type  #gof
-        self.cb["sendMessage"](to, message, type, profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='ssia{ss}s', out_signature='')
-    def setPresence(self, to="", show="", priority=0, statuses={}, profile_key='@DEFAULT@'):
-        self.cb["setPresence"](to, show, priority, statuses, profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='sss', out_signature='')
-    def subscription(self, type, entity, profile_key='@DEFAULT@'):
-        self.cb["subscription"](type, entity, profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='ssss', out_signature='')
-    def setParam(self, name, value, category, profile_key='@DEFAULT@'):
-        self.cb["setParam"](unicode(name), unicode(value), unicode(category), profile_key)
-        
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='sss', out_signature='s')
-    def getParamA(self, name, category="default", profile_key='@DEFAULT@'):
-        return self.cb["getParamA"](name, category, profile_key = profile_key)
-
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='s', out_signature='s')
-    def getParamsUI(self, profile_key='@DEFAULT@'):
-        return self.cb["getParamsUI"](profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='s', out_signature='s')
-    def getParams(self, profile_key='@DEFAULT@'):
-        return self.cb["getParams"](profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='ss', out_signature='s')
-    def getParamsForCategory(self, category, profile_key='@DEFAULT@'):
-        return self.cb["getParamsForCategory"](category, profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='', out_signature='as')
-    def getParamsCategories(self):
-        return self.cb["getParamsCategories"]()
-
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='ssi', out_signature='a{i(ss)}')
-    def getHistory(self, from_jid, to_jid, size):
-        debug("History asked for %s", to_jid)
-        return self.cb["getHistory"](from_jid, to_jid, size)
-    
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='ss', out_signature='')
-    def addContact(self, entity, profile_key='@DEFAULT@'):
-        debug("Subscription asked for %s (profile %s)", entity, profile_key)
-        return self.cb["addContact"](entity, profile_key)
-    
-    @dbus.service.method(const_INT_PREFIX+const_COMM_SUFFIX,
-                         in_signature='ss', out_signature='')
-    def delContact(self, entity, profile_key='@DEFAULT@'):
-        debug("Unsubscription asked for %s (profile %s)", entity, profile_key)
-        return self.cb["delContact"](entity, profile_key)
-
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='sa{ss}s', out_signature='s')
-    def launchAction(self, type, data, profile_key='@DEFAULT@'):
-        return self.cb["launchAction"](type, data, profile_key)
-    
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='sba{ss}', out_signature='')
-    def confirmationAnswer(self, id, accepted, data):
-        debug("Answer for confirmation [%s]: %s", id, "Accepted" if accepted else "Refused")
-        return self.cb["confirmationAnswer"](id, accepted, data)
-    
-
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='s', out_signature='a{ss}')
-    def getProgress(self, id):
-        #debug("Progress asked for %s", id)
-        return self.cb["getProgress"](id)
-
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='', out_signature='a(sss)')
-    def getMenus(self):
-        return self.cb["getMenus"]()
-    
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='sss', out_signature='s')
-    def getMenuHelp(self, category, name, type="NORMAL"):
-        return self.cb["getMenuHelp"](category, name, type)
-    
-    @dbus.service.method(const_INT_PREFIX+const_REQ_SUFFIX,
-                         in_signature='ssss', out_signature='s')
-    def callMenu(self, category, name, type, profile_key):
-        return self.cb["callMenu"](category, name, type, profile_key)
-    
-    def __attribute_string(self, in_sign):
-        i=0
-        idx=0
-        attr_string=""
-        while i<len(in_sign):
-            if in_sign[i] not in ['b','y','n','i','x','q','u','t','d','s','a']:
-                raise Exception  #FIXME: create an exception here (unmanaged attribute type)
-
-            attr_string += ("" if idx==0 else ",") + ("arg_%i" % idx)
-            idx+=1
-
-            if in_sign[i] == 'a':
-                i+=1
-                if in_sign[i]!='{' and in_sign[i]!='(': #FIXME: must manage tuples out of arrays
-                    i+=1
-                    continue #we have a simple type for the array
-                while (True): #we have a dict or a list of tuples
-                    i+=1
-                    if i>=len(in_sign):
-                        raise Exception  #FIXME: create an exception here (the '}' is not presend)
-                    if in_sign[i] == '}' or in_sign[i] == ')':
-                        break
-            i+=1
-        return attr_string
-
-
-
-    def addMethod(self, name, int_suffix, in_sign, out_sign):
-        """Dynamically add a method to Dbus Bridge"""
-        #FIXME: Better way ???
-        attributes = self.__attribute_string(in_sign)
-
-        code = compile ('def '+name+' (self,'+attributes+'): return self.cb["'+name+'"]('+attributes+')', '<DBus bridge>','exec')
-        exec (code)
-        method = locals()[name]
-        setattr(DbusObject, name, dbus.service.method(
-            const_INT_PREFIX+int_suffix, in_signature=in_sign, out_signature=out_sign)(method))
-    
-    def addSignal(self, name, int_suffix, signature):
-        """Dynamically add a signal to Dbus Bridge"""
-        #FIXME: Better way ???
-        attributes = self.__attribute_string(signature)
-
-        code = compile ('def '+name+' (self,'+attributes+'): debug ("'+name+' signal")', '<DBus bridge>','exec')
-        exec (code)
-        signal = locals()[name]
-        setattr(DbusObject, name, dbus.service.signal(
-            const_INT_PREFIX+int_suffix, signature=signature)(signal))
-
-class DBusBridge(Bridge):
-    def __init__(self):
-        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-        Bridge.__init__(self)
-        info ("Init DBus...")
-        self.session_bus = dbus.SessionBus()
-        self.dbus_name = dbus.service.BusName(const_INT_PREFIX, self.session_bus)
-        self.dbus_bridge = DbusObject(self.session_bus, '/org/goffi/SAT/bridge')
-
-    def connected(self, profile):
-        self.dbus_bridge.connected(profile)
-    
-    def disconnected(self, profile):
-        self.dbus_bridge.disconnected(profile)
-    
-    def newContact(self, contact, attributes, groups, profile):
-        self.dbus_bridge.newContact(contact, attributes, groups, profile)
-
-    def newMessage(self, from_jid, msg, type='chat', to='', profile='@NONE@'):
-        debug("sending message...")
-        self.dbus_bridge.newMessage(from_jid, msg, type, to, profile)
-
-    def newAlert(self, msg, title="", alert_type="INFO", profile='@NONE@'):
-        self.dbus_bridge.newAlert(msg, title, alert_type, profile)
-    
-    def presenceUpdate(self, entity, show, priority, statuses, profile):
-        debug("updating presence for %s",entity)
-        self.dbus_bridge.presenceUpdate(entity, show, priority, statuses, profile)
-    
-    def roomJoined(self, room_id, room_service, room_nicks, user_nick, profile):
-        self.dbus_bridge.roomJoined(room_id, room_service, room_nicks, user_nick, profile)
-
-    def subscribe(self, sub_type, entity, profile):
-        debug("subscribe request for %s",entity)
-        self.dbus_bridge.subscribe(sub_type, entity, profile)
-
-    def paramUpdate(self, name, value, category, profile):
-        debug("updating param [%s] %s ", category, name)
-        self.dbus_bridge.paramUpdate(name, value, category, profile)
-
-    def contactDeleted(self, entity, profile):
-        debug("sending contact deleted signal %s ", entity)
-        self.dbus_bridge.contactDeleted(entity, profile)
-
-    def askConfirmation(self, type, id, data):
-        self.dbus_bridge.askConfirmation(type, id, data)
-
-    def actionResult(self, type, id, data):
-        self.dbus_bridge.actionResult(type, id, data)
-
-    def actionResultExt(self, type, id, data):
-        self.dbus_bridge.actionResultExt(type, id, data)
-
-    def updatedValue(self, name, value):
-        self.dbus_bridge.updatedValue(name, value)
-
-    def register(self, name, callback):
-        debug("registering DBus bridge method [%s]", name)
-        self.dbus_bridge.register(name, callback)
-
-    def addMethod(self, name, int_suffix, in_sign, out_sign, method):
-        """Dynamically add a method to Dbus Bridge"""
-        print ("Adding method [%s] to DBus bridge" % name)
-        self.dbus_bridge.addMethod(name, int_suffix, in_sign, out_sign)
-        self.register(name, method)
-
-    def addSignal(self, name, int_suffix, signature):
-        self.dbus_bridge.addSignal(name, int_suffix, signature)
-        setattr(DBusBridge, name, getattr(self.dbus_bridge, name))
-
--- a/src/sat/bridge/bridge.py	Wed Dec 29 01:06:29 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-#!/usr/bin/python
-#-*- coding: utf-8 -*-
-
-"""
-SAT: a jabber client
-Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
-
-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, either version 3 of the License, 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 General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-from logging import debug, info, error
-
-class Bridge:
-    def __init__(self):
-        info ("Bridge initialization")
-
-    ##signals
-    def newContact(self, contact): 
-        raise NotImplementedError
-
-    def newMessage(self, from_jid, msg, type='chat'):
-        raise NotImplementedError
-
-    def presenceUpdate(self, type, jid, show, status, priority):
-        raise NotImplementedError
-
-    def paramUpdate(self, name, value):
-        raise NotImplementedError
-
-
-    ##methods
-    def connect(self):
-        raise NotImplementedError
-
-    def getContacts(self):
-        raise NotImplementedError
-    
-    def getPresenceStatus(self):
-        raise NotImplementedError
-
-    def sendMessage(self):
-        raise NotImplementedError
-
-    def setPresence(self, to="", type="", show="", status="", priority=0):
-        raise NotImplementedError
-
-    def setParam(self, name, value, namespace):
-        raise NotImplementedError
-        
-    def getParam(self, name, namespace):
-        raise NotImplementedError
-
-    def getParams(self, namespace):
-        raise NotImplementedError
-    
-    def getHistory(self, from_jid, to_jid, size):
-        raise NotImplementedError
--- a/src/sat/tools/games.py	Wed Dec 29 01:06:29 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-SAT: a jabber client
-Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
-
-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, either version 3 of the License, 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 General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-from logging import debug, info, error
-
-"""This library help manage general games (e.g. card games)"""
-
-    
-suits_order = ['pique', 'coeur', 'trefle', 'carreau', 'atout'] #I have switched the usual order 'trefle' and 'carreau' because card are more easy to see if suit colour change (black, red, black, red)
-values_order = map(str,range(1,11))+["valet","cavalier","dame","roi"]
-
-class TarotCard():
-    """This class is used to represent a car logically"""
-    #TODO: move this in a library in tools, and share this with frontends (e.g. card_game in wix use the same class)
-
-    def __init__(self, tuple_card):
-        """@param tuple_card: tuple (suit, value)"""
-        self.suit, self.value = tuple_card
-        self.bout = True if self.suit=="atout" and self.value in ["1","21","excuse"] else False
-        if self.bout or self.value == "roi":
-            self.points = 4.5
-        elif self.value == "dame":
-            self.points = 3.5
-        elif self.value == "cavalier":
-            self.points = 2.5
-        elif self.value == "valet":
-            self.points = 1.5
-        else:
-            self.points = 0.5
-
-    def get_tuple(self):
-        return (self.suit,self.value)
-
-    @staticmethod
-    def from_tuples(tuple_list):
-        result = []
-        for card_tuple in tuple_list:
-            result.append(TarotCard(card_tuple))
-        return result
-
-    def __cmp__(self, other):
-        if other == None:
-            return 1
-        if self.suit != other.suit:
-            idx1 = suits_order.index(self.suit)
-            idx2 = suits_order.index(other.suit)
-            return idx1.__cmp__(idx2)
-        if self.suit == 'atout':
-            if self.value == other.value == 'excuse':
-                return 0
-            if self.value == 'excuse':
-                return -1
-            if other.value == 'excuse':
-                return 1
-            return int(self.value).__cmp__(int(other.value))
-        #at this point we have the same suit which is not 'atout'
-        idx1 = values_order.index(self.value)
-        idx2 = values_order.index(other.value)
-        return idx1.__cmp__(idx2)
-
-    def __str__(self):
-        return "[%s,%s]" % (self.suit, self.value)
--- a/src/sat/tools/jid.py	Wed Dec 29 01:06:29 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-SAT: a jabber client
-Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
-
-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, either version 3 of the License, 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 General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-
-
-class JID(unicode):
-    """This class help manage JID (Node@Domaine/Resource)"""
-
-    def __new__(cls, jid):
-        self = unicode.__new__(cls, jid)
-        self.__parse()
-        return self
-
-    def __parse(self):
-        """find node domaine and resource"""
-        node_end=self.find('@')
-        if node_end<0:
-            node_end=0
-        domain_end=self.find('/')
-        if domain_end<1:
-            domain_end=len(self)
-        self.node=self[:node_end]
-        self.domain=self[(node_end+1) if node_end else 0:domain_end]
-        self.resource=self[domain_end+1:]
-        if not node_end:
-            self.short=self
-        else:
-            self.short=self.node+'@'+self.domain
-
-    def is_valid(self):
-        """return True if the jid is xmpp compliant"""
-        #FIXME: always return True for the moment
-        return True
--- a/src/sat/tools/memory.py	Wed Dec 29 01:06:29 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,655 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-SAT: a jabber client
-Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
-
-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, either version 3 of the License, 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 General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-from __future__ import with_statement
-
-import os.path
-import time
-import cPickle as pickle
-from xml.dom import minidom
-from logging import debug, info, error
-import pdb
-from twisted.internet import defer
-from twisted.words.protocols.jabber import jid
-from sat.tools.xml_tools import paramsXml2xmlUI
-
-SAVEFILE_PARAM_XML="/param" #xml parameters template
-SAVEFILE_PARAM_DATA="/param" #individual & general parameters; _ind and _gen suffixes will be added
-SAVEFILE_HISTORY="/history"
-SAVEFILE_PRIVATE="/private"  #file used to store misc values (mainly for plugins)
-
-class Param():
-    """This class manage parameters with xml"""
-    ### TODO: add desciption in params
-    
-    #TODO: move Watched in a plugin
-    default_xml = u"""
-    <params>
-    <general>
-    </general>
-    <individual>
-        <category name="Connection" label="%(category_connection)s">
-            <param name="JabberID" value="goffi@necton2.int/TestScript" type="string" />
-            <param name="Password" value="toto" type="password" />
-            <param name="Server" value="necton2.int" type="string" />
-            <param name="NewAccount" value="%(label_NewAccount)s" type="button" callback_id="registerNewAccount"/>
-            <param name="autoconnect" label="%(label_autoconnect)s" value="true" type="bool" />
-            <param name="autodisconnect" label="%(label_autodisconnect)s" value="false"  type="bool" />
-        </category>
-        <category name="Misc" label="%(category_misc)s">
-            <param name="Watched" value="test@Jabber.goffi.int" type="string" />
-        </category>
-    </individual>
-    </params>
-    """ % {'category_connection': _("Connection"),
-           'label_NewAccount': _("Register new account"),
-           'label_autoconnect': _('Connect on frontend startup'),
-           'label_autodisconnect': _('Disconnect on frontend closure'),
-           'category_misc': _("Misc")
-          }
-
-    def load_default_params(self):
-        self.dom = minidom.parseString(Param.default_xml.encode('utf-8'))
-
-    def load_xml(self, file):
-        """Load parameters template from file"""
-        self.dom = minidom.parse(file)
-    
-    def load_data(self, file):
-        """Load parameters data from file"""
-        file_ind = file + '_ind'
-        file_gen = file + '_gen'
-
-        if os.path.exists(file_gen):
-            try:
-                with open(file_gen, 'r') as file_gen_pickle:
-                    self.params_gen=pickle.load(file_gen_pickle)
-                debug(_("general params data loaded"))
-            except:
-                error (_("Can't load general params data !"))
-        
-        if os.path.exists(file_ind):
-            try:
-                with open(file_ind, 'r') as file_ind_pickle:
-                    self.params=pickle.load(file_ind_pickle)
-                debug(_("individual params data loaded"))
-            except:
-                error (_("Can't load individual params data !"))
-    
-    def save_xml(self, file):
-        """Save parameters template to xml file"""
-        with open(file, 'wb') as xml_file:
-            xml_file.write(self.dom.toxml('utf-8'))
-
-    def save_data(self, file):
-        """Save parameters data to file"""
-        #TODO: save properly in a separate file/database,
-        # use different behaviour depending of the data type (e.g. password encrypted)
-        
-        #general params
-        with open(file+'_gen', 'w') as param_gen_pickle:
-            pickle.dump(self.params_gen, param_gen_pickle)
-
-        #then individual params
-        with open(file+'_ind', 'w') as param_ind_pickle:
-            pickle.dump(self.params, param_ind_pickle)
-
-    def __init__(self, host):
-        debug("Parameters init")
-        self.host = host
-        self.default_profile = None
-        self.params = {}
-        self.params_gen = {}
-        host.set_const('savefile_param_xml', SAVEFILE_PARAM_XML)
-        host.set_const('savefile_param_data', SAVEFILE_PARAM_DATA)
-        host.registerGeneralCB("registerNewAccount", host.registerNewAccountCB)
-
-    def getProfilesList(self):
-        return self.params.keys()
-
-    def createProfile(self, name):
-        """Create a new profile
-        @param name: Name of the profile"""
-        if self.params.has_key(name):
-            info (_('The profile name already exists'))
-            return 1
-        self.params[name]={}
-        return 0
-
-    def deleteProfile(self, name):
-        """Delete an existing profile
-        @param name: Name of the profile"""
-        if not self.params.has_key(name):
-            error (_('Trying to delete an unknown profile'))
-            return 1
-        del self.params[name]
-        return 0
-
-    def getProfileName(self, profile_key):
-        """return profile according to profile_key
-        @param profile_key: profile name or key which can be
-                            @ALL@ for all profiles
-                            @DEFAULT@ for default profile
-        @return: requested profile name or None if it doesn't exist"""
-        if profile_key=='@DEFAULT@':
-            if not self.params:
-                return ""
-            default = self.host.memory.getPrivate('Profile_default')
-            if not default or not default in self.params:
-                info(_('No default profile, returning first one')) #TODO: manage real default profile
-                default = self.params.keys()[0]
-                self.host.memory.setPrivate('Profile_default', default)
-            return default #FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists
-        if not self.params.has_key(profile_key):
-            info (_('Trying to access an unknown profile'))
-            return ""
-        return profile_key
-
-    def __get_unique_node(self, parent, tag, name):
-        """return node with given tag
-        @param parent: parent of nodes to check (e.g. documentElement)
-        @param tag: tag to check (e.g. "category")
-        @param name: name to check (e.g. "JID")
-        @return: node if it exist or None
-        """
-        for node in parent.childNodes:
-            if node.nodeName == tag and node.getAttribute("name") == name:
-                #the node already exists
-                return node
-        #the node is new
-        return None
-
-    def importParams(self, xml):
-        """import xml in parameters, do nothing if the param already exist
-        @param xml: parameters in xml form"""
-        src_dom = minidom.parseString(xml.encode('utf-8'))
-
-        def import_node(tgt_parent, src_parent):
-            for child in src_parent.childNodes:
-                if child.nodeName == '#text':
-                    continue
-                node = self.__get_unique_node(tgt_parent, child.nodeName, child.getAttribute("name"))
-                if not node: #The node is new
-                    tgt_parent.appendChild(child)
-                else:
-                    import_node(node, child)
-
-        import_node(self.dom.documentElement, src_dom.documentElement)
-
-    def __default_ok(self, value, name, category):
-        #FIXME: gof: will not work with individual parameters
-        self.setParam(name, value, category) #FIXME: better to set param xml value ???
-
-    def __default_ko(self, failure, name, category):
-        error (_("Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category':category, 'name':name, 'reason':str(failure.value)})
-
-    def setDefault(self, name, category, callback, errback=None):
-        """Set default value of parameter
-        'default_cb' attibute of parameter must be set to 'yes'
-        @param name: name of the parameter
-        @param category: category of the parameter
-        @param callback: must return a string with the value (use deferred if needed)
-        @param errback: must manage the error with args failure, name, category
-        """
-        #TODO: send signal param update if value changed
-        node =  self.__getParamNode(name, category, '@ALL@')
-        if not node:
-            error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name':name, 'category':category})
-            return
-        if node[1].getAttribute('default_cb') == 'yes':
-            del node[1].attributes['default_cb']
-            d = defer.maybeDeferred(callback)
-            d.addCallback(self.__default_ok, name, category)
-            d.addErrback(errback or self.__default_ko, name, category)
-
-    def getParamA(self, name, category, attr="value", profile_key="@DEFAULT@"):
-        """Helper method to get a specific attribute
-           @param name: name of the parameter
-           @param category: category of the parameter
-           @param attr: name of the attribute (default: "value")
-           @param profile: owner of the param (@ALL@ for everyone)
-           
-           @return: attribute"""
-        node = self.__getParamNode(name, category)
-        if not node:
-            error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name':name, 'category':category})
-            return None
-
-        if node[0] == 'general':
-            value = self.__getParam(None, category, name, 'general')
-            return value or node[1].getAttribute(attr)
-        
-        assert(node[0] == 'individual')
-
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Requesting a param for an non-existant profile'))
-            return None
-
-        if attr == "value": 
-            return self.__getParam(profile, category, name) or node[1].getAttribute(attr)
-        else:
-            return node[1].getAttribute(attr)
-
-
-    def __getParam(self, profile, category, name, type='individual'):
-        """Return the param, or None if it doesn't exist
-        @param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@)
-        @param category: param category
-        @param name: param name
-        """
-        if type == 'general':
-            if self.params_gen.has_key((category, name)):
-                return self.params_gen[(category, name)]
-            return None  #This general param has the default value
-        assert (type == 'individual')
-        if not self.params.has_key(profile) or not self.params[profile].has_key((category, name)):
-            return None
-        return self.params[profile][(category, name)]
-
-    def __constructProfileXml(self, profile):
-        """Construct xml for asked profile, filling values when needed
-        /!\ as noticed in doc, don't forget to unlink the minidom.Document
-        @param profile: profile name (not key !)
-        @return: minidom.Document of the profile xml (cf warning above)
-        """
-        prof_xml = minidom.parseString('<params/>')
-        
-        for type_node in self.dom.documentElement.childNodes:
-            if type_node.nodeName == 'general' or type_node.nodeName == 'individual':  #we use all params, general and individual
-                for cat_node in type_node.childNodes:
-                    if cat_node.nodeName == 'category':
-                        category = cat_node.getAttribute('name')
-                        cat_copy = cat_node.cloneNode(True) #we make a copy for the new xml
-                        params = cat_copy.getElementsByTagName("param")
-                        for param_node in params:
-                            name = param_node.getAttribute('name')
-                            profile_value = self.__getParam(profile, category, name, type_node.nodeName)
-                            if profile_value:  #there is a value for this profile, we must change the default
-                                param_node.setAttribute('value', profile_value)
-                        prof_xml.documentElement.appendChild(cat_copy)
-        return prof_xml
-
-
-    def getParamsUI(self, profile_key='@DEFAULT@'):
-        """Return a SàT XMLUI for parameters, with given profile"""
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_("Asking params for inexistant profile"))
-            return ""
-        param_xml = self.getParams(profile)
-        return paramsXml2xmlUI(param_xml)
-
-    def getParams(self, profile_key='@DEFAULT@'):
-        """Construct xml for asked profile
-        Take params xml as skeleton"""
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_("Asking params for inexistant profile"))
-            return ""
-        prof_xml = self.__constructProfileXml(profile) 
-        return_xml = prof_xml.toxml()
-        prof_xml.unlink()
-
-        return return_xml
-
-    def getParamsForCategory(self, category, profile_key='@DEFAULT@'):
-        """Return node's xml for selected category"""
-        #TODO: manage category of general type (without existant profile)
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_("Asking params for inexistant profile"))
-            return ""
-        prof_xml = self.__constructProfileXml(profile) 
-        
-        for node in prof_xml.getElementsByTagName("category"):
-            if node.nodeName == "category" and node.getAttribute("name") == category:
-                result = node.toxml()
-                prof_xml.unlink()
-                return result
-
-        prof_xml.unlink()
-        return "<category />"
-
-    def __getParamNode(self, name, category, type="@ALL@"): #FIXME: is type useful ?
-        """Return a node from the param_xml
-        @param name: name of the node
-        @param category: category of the node
-        @type: keyword for search:
-                                    @ALL@ search everywhere
-                                    @GENERAL@ only search in general type
-                                    @INDIVIDUAL@ only search in individual type
-        @return: a tuple with the node type and the the node, or None if not found"""
-
-        for type_node in self.dom.documentElement.childNodes:
-            if ( ((type == "@ALL@" or type == "@GENERAL@") and type_node.nodeName == 'general') 
-            or ( (type == "@ALL@" or type == "@INDIVIDUAL@") and type_node.nodeName == 'individual') ):
-                for node in type_node.getElementsByTagName('category'):
-                    if node.getAttribute("name") == category:
-                        params = node.getElementsByTagName("param")
-                        for param in params:
-                            if param.getAttribute("name") == name:
-                                return (type_node.nodeName, param)
-        return None
-        
-    def getParamsCategories(self):
-        """return the categories availables"""
-        categories=[]
-        for cat in self.dom.getElementsByTagName("category"):
-            categories.append(cat.getAttribute("name"))
-        return categories
-
-    def setParam(self, name, value, category, profile_key='@DEFAULT@'):
-        """Set a parameter, return None if the parameter is not in param xml"""
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Trying to set parameter for an unknown profile'))
-            return #TODO: throw an error
-
-        node = self.__getParamNode(name, category, '@ALL@')
-        if not node:
-            error(_('Requesting an unknown parameter (%(category)s/%(name)s)') % {'category':category, 'name':name})
-            return
-        
-        if node[0] == 'general':
-            self.params_gen[(category, name)] = value
-            self.host.bridge.paramUpdate(name, value, category, profile) #TODO: add profile in signal
-            return
-        
-        assert (node[0] == 'individual')
-        
-        type = node[1].getAttribute("type")
-        if type=="button":
-            print "clique",node.toxml()
-        else:
-            self.params[profile][(category, name)] = value
-            self.host.bridge.paramUpdate(name, value, category, profile) #TODO: add profile in signal
-
-class Memory:
-    """This class manage all persistent informations"""
-
-    def __init__(self, host):
-        info (_("Memory manager init"))
-        self.host = host
-        self.contacts={}
-        self.presenceStatus={}
-        self.subscriptions={}
-        self.params=Param(host)
-        self.history={}  #used to store chat history (key: short jid)
-        self.private={}  #used to store private value
-        host.set_const('savefile_history', SAVEFILE_HISTORY)
-        host.set_const('savefile_private', SAVEFILE_PRIVATE)
-        self.load()
-
-    def load(self):
-        """Load parameters and all memory things from file/db"""
-        param_file_xml = os.path.expanduser(self.host.get_const('local_dir')+
-                                        self.host.get_const('savefile_param_xml'))
-        param_file_data = os.path.expanduser(self.host.get_const('local_dir')+
-                                        self.host.get_const('savefile_param_data'))
-        history_file = os.path.expanduser(self.host.get_const('local_dir')+
-                                        self.host.get_const('savefile_history'))
-        private_file = os.path.expanduser(self.host.get_const('local_dir')+
-                                        self.host.get_const('savefile_private'))
-
-        #parameters
-        if os.path.exists(param_file_xml):
-            try:
-                self.params.load_xml(param_file_xml)
-                debug(_("params template loaded"))
-            except:
-                error (_("Can't load params template !"))
-                self.params.load_default_params()
-        else:
-            info (_("No params template, using default template"))
-            self.params.load_default_params()
-
-        try:
-            self.params.load_data(param_file_data)
-            debug(_("params loaded"))
-        except:
-            error (_("Can't load params !"))
-
-        #history
-        if os.path.exists(history_file):
-            try:
-                with open(history_file, 'r') as history_pickle:
-                    self.history=pickle.load(history_pickle)
-                debug(_("history loaded"))
-            except:
-                error (_("Can't load history !"))
-
-        #private
-        if os.path.exists(private_file):
-            try:
-                with open(private_file, 'r') as private_pickle:
-                    self.private=pickle.load(private_pickle)
-                debug(_("private values loaded"))
-            except:
-                error (_("Can't load private values !"))
-
-    def save(self):
-        """Save parameters and all memory things to file/db"""
-        #TODO: need to encrypt files (at least passwords !) and set permissions
-        param_file_xml = os.path.expanduser(self.host.get_const('local_dir')+
-                                        self.host.get_const('savefile_param_xml'))
-        param_file_data = os.path.expanduser(self.host.get_const('local_dir')+
-                                        self.host.get_const('savefile_param_data'))
-        history_file = os.path.expanduser(self.host.get_const('local_dir')+
-                                        self.host.get_const('savefile_history'))
-        private_file = os.path.expanduser(self.host.get_const('local_dir')+
-                                        self.host.get_const('savefile_private'))
-        
-        self.params.save_xml(param_file_xml)
-        self.params.save_data(param_file_data)
-        debug(_("params saved"))
-        with open(history_file, 'w') as history_pickle:
-            pickle.dump(self.history, history_pickle)
-        debug(_("history saved"))
-        with open(private_file, 'w') as private_pickle:
-            pickle.dump(self.private, private_pickle)
-        debug(_("private values saved"))
-
-    def getProfilesList(self):
-        return self.params.getProfilesList()
-
-
-    def getProfileName(self, profile_key):
-        """Return name of profile from keyword
-        @param profile_key: can be the profile name or a keywork (like @DEFAULT@)
-        @return: profile name or None if it doesn't exist"""
-        return self.params.getProfileName(profile_key)
-
-    def createProfile(self, name):
-        """Create a new profile
-        @param name: Profile name
-        """
-        return self.params.createProfile(name)
-    
-    def deleteProfile(self, name):
-        """Delete an existing profile
-        @param name: Name of the profile"""
-        return self.params.deleteProfile(name)
-
-    def addToHistory(self, me_jid, from_jid, to_jid, type, message):
-        me_short=me_jid.userhost()
-        from_short=from_jid.userhost()
-        to_short=to_jid.userhost()
-
-        if from_jid==me_jid:
-            key=to_short
-        else:
-            key=from_short
-
-        if not self.history.has_key(me_short):
-            self.history[me_short]={}
-        if not self.history[me_short].has_key(key):
-            self.history[me_short][key]={}
-
-        self.history[me_short][key][int(time.time())] = (from_jid.full(), message)
-        
-    def getHistory(self, from_jid, to_jid, size):
-        ret={}
-        if not self.history.has_key(from_jid):
-            error(_("source JID not found !"))
-            #TODO: throw an error here
-            return {}
-        if not self.history[from_jid].has_key(to_jid):
-            error(_("dest JID not found !"))
-            #TODO: throw an error here
-            return {}
-        stamps=self.history[from_jid][to_jid].keys()
-        stamps.sort()
-        for stamp in stamps[-size:]:
-            ret[stamp]=self.history[from_jid][to_jid][stamp]
-
-        return ret
-
-    def setPrivate(self, key, value):
-        """Save a misc private value (mainly useful for plugins)"""
-        self.private[key] = value
-
-    def getPrivate(self, key):
-        """return a private value
-        @param key: name of wanted value
-        @return: value or None if value don't exist"""
-        if self.private.has_key(key):
-            return self.private[key]
-        return None
-
-
-    def addContact(self, contact_jid, attributes, groups, profile_key='@DEFAULT@'):
-        debug("Memory addContact: %s",contact_jid.userhost())
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error (_('Trying to add a contact to a non-existant profile'))
-            return
-        assert(isinstance(attributes,dict))
-        assert(isinstance(groups,set))
-        if not self.contacts.has_key(profile):
-            self.contacts[profile] = {}
-        self.contacts[profile][contact_jid.userhost()]=[attributes, groups]
-
-    def delContact(self, contact_jid, profile_key='@DEFAULT@'):
-        debug("Memory delContact: %s",contact_jid.userhost())
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error (_('Trying to delete a contact for a non-existant profile'))
-            return
-        if self.contacts.has_key(profile) and self.contacts[profile].has_key(contact_jid.userhost()):
-            del self.contacts[profile][contact_jid.userhost()]
-    
-    def getContact(self, contact_jid, profile_key='@DEFAULT@'):
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Asking a contact for a non-existant profile'))
-            return None
-        if self.contacts.has_key(profile) and self.contacts[profile].has_key(contact_jid.userhost()):
-            self.contacts[profile][contact_jid.userhost()]
-        else:
-            return None
-    
-    def getContacts(self, profile_key='@DEFAULT@'):
-        """Return list of contacts for given profile
-        @param profile_key: profile key
-        @return list of [contact, attr, groups]"""
-        debug ("Memory getContact OK (%s)", self.contacts)
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Asking contacts for a non-existant profile'))
-            return []
-        ret=[]
-        for contact in self.contacts[profile]:
-            attr, groups = self.contacts[profile][contact]
-            ret.append([contact, attr, groups ])
-        return ret
-    
-    def addPresenceStatus(self, contact_jid, show, priority, statuses, profile_key='@DEFAULT@'):
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Trying to add presence status to a non-existant profile'))
-            return
-        if not self.presenceStatus.has_key(profile):
-            self.presenceStatus[profile] = {}
-        if not self.presenceStatus[profile].has_key(contact_jid.userhost()):
-            self.presenceStatus[profile][contact_jid.userhost()] = {}
-        resource = jid.parse(contact_jid.full())[2] or ''
-        self.presenceStatus[profile][contact_jid.userhost()][resource] = (show, priority, statuses)
-
-    def addWaitingSub(self, type, contact_jid, profile_key):
-        """Called when a subcription request is received"""
-        profile = self.getProfileName(profile_key)
-        assert(profile)
-        if not self.subscriptions.has_key(profile):
-            self.subscriptions[profile] = {}
-        self.subscriptions[profile][contact_jid] = type
-    
-    def delWaitingSub(self, contact_jid, profile_key):
-        """Called when a subcription request is finished"""
-        profile = self.getProfileName(profile_key)
-        assert(profile)
-        if self.subscriptions.has_key(profile) and self.subscriptions[profile].has_key(contact_jid):
-            del self.subscriptions[profile][contact_jid]
-    
-    def getWaitingSub(self, profile_key='@DEFAULT@'):
-        """Called to get a list of currently waiting subscription requests"""
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Asking waiting subscriptions for a non-existant profile'))
-            return {}
-        if not self.subscriptions.has_key(profile):
-            return {}
-        
-        return self.subscriptions[profile]
-
-    def getPresenceStatus(self, profile_key='@DEFAULT@'):
-        profile = self.getProfileName(profile_key)
-        if not profile:
-            error(_('Asking contacts for a non-existant profile'))
-            return {}
-        if not self.presenceStatus.has_key(profile):
-            self.presenceStatus[profile] = {}
-        debug ("Memory getPresenceStatus (%s)", self.presenceStatus[profile])
-        return self.presenceStatus[profile]
-
-    def getParamA(self, name, category, attr="value", profile_key="@DEFAULT@"):
-        return self.params.getParamA(name, category, attr, profile_key)
-    
-    def getParamsUI(self, profile_key='@DEFAULT@'):
-        return self.params.getParamsUI(profile_key)
-  
-    def getParams(self, profile_key='@DEFAULT@'):
-        return self.params.getParams(profile_key) 
-    
-    def getParamsForCategory(self, category, profile_key='@DEFAULT@'):
-        return self.params.getParamsForCategory(category, profile_key) 
-    
-    def getParamsCategories(self):
-        return self.params.getParamsCategories()
-    
-    def setParam(self, name, value, category, profile_key='@DEFAULT@'):
-        return self.params.setParam(name, value, category, profile_key)
-
-    def importParams(self, xml):
-        return self.params.importParams(xml)
-    
-    def setDefault(self, name, category, callback, errback=None):
-        return self.params.setDefault(name, category, callback, errback)
--- a/src/sat/tools/xml_tools.py	Wed Dec 29 01:06:29 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,302 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-SAT: a jabber client
-Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
-
-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, either version 3 of the License, 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 General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-from logging import debug, info, error
-from xml.dom import minidom
-from wokkel import data_form
-import pdb
-
-"""This library help manage XML used in SàT (parameters, registration, etc) """
-
-    
-def dataForm2xml(form):
-    """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml"""
-    
-    form_ui = XMLUI("form", "vertical")
-
-    if form.instructions:
-        form_ui.addText('\n'.join(form.instructions), 'instructions')
-    
-    labels = filter(lambda field:field.label,form.fieldList)
-    if labels:
-        #if there is no label, we don't need to use pairs
-        form_ui.changeLayout("pairs")
-    
-    for field in form.fieldList:
-        if field.fieldType == 'fixed':
-            __field_type = 'text'
-        elif field.fieldType == 'text-single':
-            __field_type = "string"
-        elif field.fieldType == 'text-private':
-            __field_type = "password"
-        elif field.fieldType == 'list-single':
-            __field_type = "list"
-        else:
-            error (u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType)
-            __field_type = "string"
-        
-        if labels:
-            if field.label:
-                form_ui.addLabel(field.label)
-            else:
-                form_ui.addEmpty()
-
-        elem = form_ui.addElement(__field_type, field.var, field.value, [option.value for option in field.options]) 
-    return form_ui.toXml()
-
-def tupleList2dataForm(values):
-    """convert a list of tuples (name,value) to a wokkel submit data form"""
-    form = data_form.Form('submit')
-    for value in values:
-        field = data_form.Field(var=value[0], value=value[1])
-        form.addField(field)
-
-    return form
-
-def paramsXml2xmlUI(xml):
-    """Convert the xml for parameter to a SàT XML User Interface"""
-    params_doc = minidom.parseString(xml.encode('utf-8'))
-    top = params_doc.documentElement
-    if top.nodeName != 'params':
-        error(_('INTERNAL ERROR: parameters xml not valid'))
-        assert(False)
-    param_ui = XMLUI("param", "tabs")
-    for category in top.getElementsByTagName("category"):
-        name = category.getAttribute('name')
-        label = category.getAttribute('label')
-        if not name:
-            error(_('INTERNAL ERROR: params categories must have a name'))
-            assert(False)
-        param_ui.addCategory(name, 'pairs', label=label)
-        for param in category.getElementsByTagName("param"):
-            name = param.getAttribute('name')
-            label = param.getAttribute('label')
-            if not name:
-                error(_('INTERNAL ERROR: params must have a name'))
-                assert(False)
-            type = param.getAttribute('type')
-            value = param.getAttribute('value') or None
-            callback_id = param.getAttribute('callback_id') or None
-            if type == "button":
-                param_ui.addEmpty()
-            else:
-                param_ui.addLabel(label or name)
-            param_ui.addElement(name=name, type=type, value=value, callback_id=callback_id)
-
-    return param_ui.toXml()
-
-    
-
-
-class XMLUI:
-    """This class is used to create a user interface (form/window/parameters/etc) using SàT XML"""
-
-    def __init__(self, panel_type, layout="vertical", title=None):
-        """Init SàT XML Panel
-        @param panel_type: one of
-            - window (new window)
-            - form (formulaire, depend of the frontend, usually a panel with cancel/submit buttons)
-            - param (parameters, presentatio depend of the frontend)
-        @param layout: disposition of elements, one of:
-            - vertical: elements are disposed up to bottom
-            - horizontal: elements are disposed left to right
-            - pairs: elements come on two aligned columns
-              (usually one for a label, the next for the element)
-            - tabs: elemens are in categories with tabs (notebook)
-        @param title: title or default if None
-        """
-        if not panel_type in ['window', 'form', 'param']:
-            error(_("Unknown panel type [%s]") % panel_type)
-            assert(False)
-        self.type = panel_type
-        impl = minidom.getDOMImplementation()
-
-        self.doc = impl.createDocument(None, "sat_xmlui", None)
-        top_element = self.doc.documentElement
-        top_element.setAttribute("type", panel_type)
-        if title:
-            top_element.setAttribute("title", title)
-        self.parentTabsLayout = None #used only we have 'tabs' layout
-        self.currentCategory = None #used only we have 'tabs' layout
-        self.changeLayout(layout)
-
-    def __del__(self):
-        self.doc.unlink() 
-    
-    def __createLayout(self, layout, parent=None):
-        """Create a layout element
-        @param type: layout type (cf init doc)
-        @parent: parent element or None
-        """
-        if not layout in ['vertical', 'horizontal', 'pairs', 'tabs']:
-            error (_("Unknown layout type [%s]") % layout)
-            assert (False)
-        layout_elt = self.doc.createElement('layout')
-        layout_elt.setAttribute('type',layout)
-        if parent != None:
-            parent.appendChild(layout_elt)
-        return layout_elt
-
-    def __createElem(self, type, name=None, parent = None):
-        """Create an element
-        @param type: one of
-            - empty: empty element (usefull to skip something in a layout, e.g. skip first element in a PAIRS layout)
-            - text: text to be displayed in an multi-line area, e.g. instructions
-        @param name: name of the element or None
-        @param parent: parent element or None
-        """
-        elem = self.doc.createElement('elem')
-        if name:
-            elem.setAttribute('name', name)
-        elem.setAttribute('type', type)
-        if parent != None:
-            parent.appendChild(elem)
-        return elem
-
-    def changeLayout(self, layout):
-        """Change the current layout"""
-        self.currentLayout = self.__createLayout(layout, self.currentCategory if self.currentCategory else self.doc.documentElement)
-        if layout == "tabs":
-            self.parentTabsLayout = self.currentLayout
-
-
-    def addEmpty(self, name=None):
-        """Add a multi-lines text"""
-        elem = self.__createElem('empty', name, self.currentLayout)
-    
-    def addText(self, text, name=None):
-        """Add a multi-lines text"""
-        elem = self.__createElem('text', name, self.currentLayout)
-        text = self.doc.createTextNode(text)
-        elem.appendChild(text)
-
-    def addLabel(self, text, name=None):
-        """Add a single line text, mainly useful as label before element"""
-        elem = self.__createElem('label', name, self.currentLayout)
-        elem.setAttribute('value', text)
-
-    def addString(self, name=None, value=None):
-        """Add a string box"""
-        elem = self.__createElem('string', name, self.currentLayout)
-        if value:
-            elem.setAttribute('value', value)
-    
-    def addPassword(self, name=None, value=None):
-        """Add a password box"""
-        elem = self.__createElem('password', name, self.currentLayout)
-        if value:
-            elem.setAttribute('value', value)
-
-    def addTextBox(self, name=None, value=None):
-        """Add a string box"""
-        elem = self.__createElem('textbox', name, self.currentLayout)
-        if value:
-            elem.setAttribute('value', value)
-    
-    def addBool(self, name=None, value="true"):
-        """Add a string box"""
-        assert value in ["true","false"]
-        elem = self.__createElem('bool', name, self.currentLayout)
-        elem.setAttribute('value', value)
-    
-    def addList(self, options, name=None, value=None, style=set()):
-        """Add a list of choices"""
-        styles = set(style)
-        assert (options)
-        assert (styles.issubset(['multi'])) 
-        elem = self.__createElem('list', name, self.currentLayout)
-        self.addOptions(options, elem) 
-        if value:
-            elem.setAttribute('value', value)
-        for style in styles:
-            elem.setAttribute(style, 'yes')
-
-    def addButton(self, callback_id, name, value, fields_back=[]):
-        """Add a button
-        @param callback: callback which will be called if button is pressed
-        @param name: name
-        @param value: label of the button
-        @fields_back: list of names of field to give back when pushing the button"""
-        elem = self.__createElem('button', name, self.currentLayout)
-        elem.setAttribute('callback_id', callback_id)
-        elem.setAttribute('value', value)
-        for field in fields_back:
-            fback_el = self.doc.createElement('field_back')
-            fback_el.setAttribute('name', field)
-            elem.appendChild(fback_el)
-
-
-    
-    def addElement(self, type, name = None, value = None, options = None, callback_id = None):
-        """Convenience method to add element, the params correspond to the ones in addSomething methods"""
-        if type == 'empty':
-            self.addEmpty(name)
-        elif type == 'text':
-            assert(value!=None)
-            self.addText(value, name)
-        elif type == 'label':
-            assert(value)
-            self.addLabel(value)
-        elif type == 'string':
-            self.addString(name, value)
-        elif type == 'password':
-            self.addPassword(name, value)
-        elif type == 'textbox':
-            self.addTextBox(name, value)
-        elif type == 'bool':
-            if not value:
-                value = "true"
-            self.addBool(name, value)
-        elif type == 'list':
-            self.addList(options, name, value)
-        elif type == 'button':
-            assert(callback_id and value)
-            self.addButton(callback_id, name, value)
-
-    def addOptions(self, options, parent):
-        """Add options to a multi-values element (e.g. list)
-        @param parent: multi-values element"""
-        for option in options:
-            opt = self.doc.createElement('option')
-            opt.setAttribute('value', option)
-            parent.appendChild(opt)
-
-    def addCategory(self, name, layout, label=None):
-        """Add a category to current layout (must be a tabs layout)"""
-        assert(layout != 'tabs')
-        if not self.parentTabsLayout:
-            error(_("Trying to add a category without parent tabs layout"))
-            assert(False)
-        if self.parentTabsLayout.getAttribute('type') != 'tabs':
-            error(_("parent layout of a category is not tabs"))
-            assert(False)
-
-        if not label:
-            label = name
-        self.currentCategory = cat = self.doc.createElement('category')
-        cat.setAttribute('name', name)
-        cat.setAttribute('label', label)
-        self.changeLayout(layout)
-        self.parentTabsLayout.appendChild(cat)
-
-    def toXml(self):
-        """return the XML representation of the panel""" 
-        return self.doc.toxml()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tools/games.py	Tue Jan 04 19:30:27 2011 +0100
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+SAT: a jabber client
+Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
+
+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, either version 3 of the License, 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from logging import debug, info, error
+
+"""This library help manage general games (e.g. card games)"""
+
+    
+suits_order = ['pique', 'coeur', 'trefle', 'carreau', 'atout'] #I have switched the usual order 'trefle' and 'carreau' because card are more easy to see if suit colour change (black, red, black, red)
+values_order = map(str,range(1,11))+["valet","cavalier","dame","roi"]
+
+class TarotCard():
+    """This class is used to represent a car logically"""
+    #TODO: move this in a library in tools, and share this with frontends (e.g. card_game in wix use the same class)
+
+    def __init__(self, tuple_card):
+        """@param tuple_card: tuple (suit, value)"""
+        self.suit, self.value = tuple_card
+        self.bout = True if self.suit=="atout" and self.value in ["1","21","excuse"] else False
+        if self.bout or self.value == "roi":
+            self.points = 4.5
+        elif self.value == "dame":
+            self.points = 3.5
+        elif self.value == "cavalier":
+            self.points = 2.5
+        elif self.value == "valet":
+            self.points = 1.5
+        else:
+            self.points = 0.5
+
+    def get_tuple(self):
+        return (self.suit,self.value)
+
+    @staticmethod
+    def from_tuples(tuple_list):
+        result = []
+        for card_tuple in tuple_list:
+            result.append(TarotCard(card_tuple))
+        return result
+
+    def __cmp__(self, other):
+        if other == None:
+            return 1
+        if self.suit != other.suit:
+            idx1 = suits_order.index(self.suit)
+            idx2 = suits_order.index(other.suit)
+            return idx1.__cmp__(idx2)
+        if self.suit == 'atout':
+            if self.value == other.value == 'excuse':
+                return 0
+            if self.value == 'excuse':
+                return -1
+            if other.value == 'excuse':
+                return 1
+            return int(self.value).__cmp__(int(other.value))
+        #at this point we have the same suit which is not 'atout'
+        idx1 = values_order.index(self.value)
+        idx2 = values_order.index(other.value)
+        return idx1.__cmp__(idx2)
+
+    def __str__(self):
+        return "[%s,%s]" % (self.suit, self.value)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tools/jid.py	Tue Jan 04 19:30:27 2011 +0100
@@ -0,0 +1,51 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+SAT: a jabber client
+Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
+
+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, either version 3 of the License, 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+
+
+class JID(unicode):
+    """This class help manage JID (Node@Domaine/Resource)"""
+
+    def __new__(cls, jid):
+        self = unicode.__new__(cls, jid)
+        self.__parse()
+        return self
+
+    def __parse(self):
+        """find node domaine and resource"""
+        node_end=self.find('@')
+        if node_end<0:
+            node_end=0
+        domain_end=self.find('/')
+        if domain_end<1:
+            domain_end=len(self)
+        self.node=self[:node_end]
+        self.domain=self[(node_end+1) if node_end else 0:domain_end]
+        self.resource=self[domain_end+1:]
+        if not node_end:
+            self.short=self
+        else:
+            self.short=self.node+'@'+self.domain
+
+    def is_valid(self):
+        """return True if the jid is xmpp compliant"""
+        #FIXME: always return True for the moment
+        return True
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tools/memory.py	Tue Jan 04 19:30:27 2011 +0100
@@ -0,0 +1,655 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+SAT: a jabber client
+Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
+
+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, either version 3 of the License, 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from __future__ import with_statement
+
+import os.path
+import time
+import cPickle as pickle
+from xml.dom import minidom
+from logging import debug, info, error
+import pdb
+from twisted.internet import defer
+from twisted.words.protocols.jabber import jid
+from sat.tools.xml_tools import paramsXml2xmlUI
+
+SAVEFILE_PARAM_XML="/param" #xml parameters template
+SAVEFILE_PARAM_DATA="/param" #individual & general parameters; _ind and _gen suffixes will be added
+SAVEFILE_HISTORY="/history"
+SAVEFILE_PRIVATE="/private"  #file used to store misc values (mainly for plugins)
+
+class Param():
+    """This class manage parameters with xml"""
+    ### TODO: add desciption in params
+    
+    #TODO: move Watched in a plugin
+    default_xml = u"""
+    <params>
+    <general>
+    </general>
+    <individual>
+        <category name="Connection" label="%(category_connection)s">
+            <param name="JabberID" value="goffi@necton2.int/TestScript" type="string" />
+            <param name="Password" value="toto" type="password" />
+            <param name="Server" value="necton2.int" type="string" />
+            <param name="NewAccount" value="%(label_NewAccount)s" type="button" callback_id="registerNewAccount"/>
+            <param name="autoconnect" label="%(label_autoconnect)s" value="true" type="bool" />
+            <param name="autodisconnect" label="%(label_autodisconnect)s" value="false"  type="bool" />
+        </category>
+        <category name="Misc" label="%(category_misc)s">
+            <param name="Watched" value="test@Jabber.goffi.int" type="string" />
+        </category>
+    </individual>
+    </params>
+    """ % {'category_connection': _("Connection"),
+           'label_NewAccount': _("Register new account"),
+           'label_autoconnect': _('Connect on frontend startup'),
+           'label_autodisconnect': _('Disconnect on frontend closure'),
+           'category_misc': _("Misc")
+          }
+
+    def load_default_params(self):
+        self.dom = minidom.parseString(Param.default_xml.encode('utf-8'))
+
+    def load_xml(self, file):
+        """Load parameters template from file"""
+        self.dom = minidom.parse(file)
+    
+    def load_data(self, file):
+        """Load parameters data from file"""
+        file_ind = file + '_ind'
+        file_gen = file + '_gen'
+
+        if os.path.exists(file_gen):
+            try:
+                with open(file_gen, 'r') as file_gen_pickle:
+                    self.params_gen=pickle.load(file_gen_pickle)
+                debug(_("general params data loaded"))
+            except:
+                error (_("Can't load general params data !"))
+        
+        if os.path.exists(file_ind):
+            try:
+                with open(file_ind, 'r') as file_ind_pickle:
+                    self.params=pickle.load(file_ind_pickle)
+                debug(_("individual params data loaded"))
+            except:
+                error (_("Can't load individual params data !"))
+    
+    def save_xml(self, file):
+        """Save parameters template to xml file"""
+        with open(file, 'wb') as xml_file:
+            xml_file.write(self.dom.toxml('utf-8'))
+
+    def save_data(self, file):
+        """Save parameters data to file"""
+        #TODO: save properly in a separate file/database,
+        # use different behaviour depending of the data type (e.g. password encrypted)
+        
+        #general params
+        with open(file+'_gen', 'w') as param_gen_pickle:
+            pickle.dump(self.params_gen, param_gen_pickle)
+
+        #then individual params
+        with open(file+'_ind', 'w') as param_ind_pickle:
+            pickle.dump(self.params, param_ind_pickle)
+
+    def __init__(self, host):
+        debug("Parameters init")
+        self.host = host
+        self.default_profile = None
+        self.params = {}
+        self.params_gen = {}
+        host.set_const('savefile_param_xml', SAVEFILE_PARAM_XML)
+        host.set_const('savefile_param_data', SAVEFILE_PARAM_DATA)
+        host.registerGeneralCB("registerNewAccount", host.registerNewAccountCB)
+
+    def getProfilesList(self):
+        return self.params.keys()
+
+    def createProfile(self, name):
+        """Create a new profile
+        @param name: Name of the profile"""
+        if self.params.has_key(name):
+            info (_('The profile name already exists'))
+            return 1
+        self.params[name]={}
+        return 0
+
+    def deleteProfile(self, name):
+        """Delete an existing profile
+        @param name: Name of the profile"""
+        if not self.params.has_key(name):
+            error (_('Trying to delete an unknown profile'))
+            return 1
+        del self.params[name]
+        return 0
+
+    def getProfileName(self, profile_key):
+        """return profile according to profile_key
+        @param profile_key: profile name or key which can be
+                            @ALL@ for all profiles
+                            @DEFAULT@ for default profile
+        @return: requested profile name or None if it doesn't exist"""
+        if profile_key=='@DEFAULT@':
+            if not self.params:
+                return ""
+            default = self.host.memory.getPrivate('Profile_default')
+            if not default or not default in self.params:
+                info(_('No default profile, returning first one')) #TODO: manage real default profile
+                default = self.params.keys()[0]
+                self.host.memory.setPrivate('Profile_default', default)
+            return default #FIXME: temporary, must use real default value, and fallback to first one if it doesn't exists
+        if not self.params.has_key(profile_key):
+            info (_('Trying to access an unknown profile'))
+            return ""
+        return profile_key
+
+    def __get_unique_node(self, parent, tag, name):
+        """return node with given tag
+        @param parent: parent of nodes to check (e.g. documentElement)
+        @param tag: tag to check (e.g. "category")
+        @param name: name to check (e.g. "JID")
+        @return: node if it exist or None
+        """
+        for node in parent.childNodes:
+            if node.nodeName == tag and node.getAttribute("name") == name:
+                #the node already exists
+                return node
+        #the node is new
+        return None
+
+    def importParams(self, xml):
+        """import xml in parameters, do nothing if the param already exist
+        @param xml: parameters in xml form"""
+        src_dom = minidom.parseString(xml.encode('utf-8'))
+
+        def import_node(tgt_parent, src_parent):
+            for child in src_parent.childNodes:
+                if child.nodeName == '#text':
+                    continue
+                node = self.__get_unique_node(tgt_parent, child.nodeName, child.getAttribute("name"))
+                if not node: #The node is new
+                    tgt_parent.appendChild(child)
+                else:
+                    import_node(node, child)
+
+        import_node(self.dom.documentElement, src_dom.documentElement)
+
+    def __default_ok(self, value, name, category):
+        #FIXME: gof: will not work with individual parameters
+        self.setParam(name, value, category) #FIXME: better to set param xml value ???
+
+    def __default_ko(self, failure, name, category):
+        error (_("Can't determine default value for [%(category)s/%(name)s]: %(reason)s") % {'category':category, 'name':name, 'reason':str(failure.value)})
+
+    def setDefault(self, name, category, callback, errback=None):
+        """Set default value of parameter
+        'default_cb' attibute of parameter must be set to 'yes'
+        @param name: name of the parameter
+        @param category: category of the parameter
+        @param callback: must return a string with the value (use deferred if needed)
+        @param errback: must manage the error with args failure, name, category
+        """
+        #TODO: send signal param update if value changed
+        node =  self.__getParamNode(name, category, '@ALL@')
+        if not node:
+            error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name':name, 'category':category})
+            return
+        if node[1].getAttribute('default_cb') == 'yes':
+            del node[1].attributes['default_cb']
+            d = defer.maybeDeferred(callback)
+            d.addCallback(self.__default_ok, name, category)
+            d.addErrback(errback or self.__default_ko, name, category)
+
+    def getParamA(self, name, category, attr="value", profile_key="@DEFAULT@"):
+        """Helper method to get a specific attribute
+           @param name: name of the parameter
+           @param category: category of the parameter
+           @param attr: name of the attribute (default: "value")
+           @param profile: owner of the param (@ALL@ for everyone)
+           
+           @return: attribute"""
+        node = self.__getParamNode(name, category)
+        if not node:
+            error(_("Requested param [%(name)s] in category [%(category)s] doesn't exist !") % {'name':name, 'category':category})
+            return None
+
+        if node[0] == 'general':
+            value = self.__getParam(None, category, name, 'general')
+            return value or node[1].getAttribute(attr)
+        
+        assert(node[0] == 'individual')
+
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_('Requesting a param for an non-existant profile'))
+            return None
+
+        if attr == "value": 
+            return self.__getParam(profile, category, name) or node[1].getAttribute(attr)
+        else:
+            return node[1].getAttribute(attr)
+
+
+    def __getParam(self, profile, category, name, type='individual'):
+        """Return the param, or None if it doesn't exist
+        @param profile: the profile name (not profile key, i.e. name and not something like @DEFAULT@)
+        @param category: param category
+        @param name: param name
+        """
+        if type == 'general':
+            if self.params_gen.has_key((category, name)):
+                return self.params_gen[(category, name)]
+            return None  #This general param has the default value
+        assert (type == 'individual')
+        if not self.params.has_key(profile) or not self.params[profile].has_key((category, name)):
+            return None
+        return self.params[profile][(category, name)]
+
+    def __constructProfileXml(self, profile):
+        """Construct xml for asked profile, filling values when needed
+        /!\ as noticed in doc, don't forget to unlink the minidom.Document
+        @param profile: profile name (not key !)
+        @return: minidom.Document of the profile xml (cf warning above)
+        """
+        prof_xml = minidom.parseString('<params/>')
+        
+        for type_node in self.dom.documentElement.childNodes:
+            if type_node.nodeName == 'general' or type_node.nodeName == 'individual':  #we use all params, general and individual
+                for cat_node in type_node.childNodes:
+                    if cat_node.nodeName == 'category':
+                        category = cat_node.getAttribute('name')
+                        cat_copy = cat_node.cloneNode(True) #we make a copy for the new xml
+                        params = cat_copy.getElementsByTagName("param")
+                        for param_node in params:
+                            name = param_node.getAttribute('name')
+                            profile_value = self.__getParam(profile, category, name, type_node.nodeName)
+                            if profile_value:  #there is a value for this profile, we must change the default
+                                param_node.setAttribute('value', profile_value)
+                        prof_xml.documentElement.appendChild(cat_copy)
+        return prof_xml
+
+
+    def getParamsUI(self, profile_key='@DEFAULT@'):
+        """Return a SàT XMLUI for parameters, with given profile"""
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_("Asking params for inexistant profile"))
+            return ""
+        param_xml = self.getParams(profile)
+        return paramsXml2xmlUI(param_xml)
+
+    def getParams(self, profile_key='@DEFAULT@'):
+        """Construct xml for asked profile
+        Take params xml as skeleton"""
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_("Asking params for inexistant profile"))
+            return ""
+        prof_xml = self.__constructProfileXml(profile) 
+        return_xml = prof_xml.toxml()
+        prof_xml.unlink()
+
+        return return_xml
+
+    def getParamsForCategory(self, category, profile_key='@DEFAULT@'):
+        """Return node's xml for selected category"""
+        #TODO: manage category of general type (without existant profile)
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_("Asking params for inexistant profile"))
+            return ""
+        prof_xml = self.__constructProfileXml(profile) 
+        
+        for node in prof_xml.getElementsByTagName("category"):
+            if node.nodeName == "category" and node.getAttribute("name") == category:
+                result = node.toxml()
+                prof_xml.unlink()
+                return result
+
+        prof_xml.unlink()
+        return "<category />"
+
+    def __getParamNode(self, name, category, type="@ALL@"): #FIXME: is type useful ?
+        """Return a node from the param_xml
+        @param name: name of the node
+        @param category: category of the node
+        @type: keyword for search:
+                                    @ALL@ search everywhere
+                                    @GENERAL@ only search in general type
+                                    @INDIVIDUAL@ only search in individual type
+        @return: a tuple with the node type and the the node, or None if not found"""
+
+        for type_node in self.dom.documentElement.childNodes:
+            if ( ((type == "@ALL@" or type == "@GENERAL@") and type_node.nodeName == 'general') 
+            or ( (type == "@ALL@" or type == "@INDIVIDUAL@") and type_node.nodeName == 'individual') ):
+                for node in type_node.getElementsByTagName('category'):
+                    if node.getAttribute("name") == category:
+                        params = node.getElementsByTagName("param")
+                        for param in params:
+                            if param.getAttribute("name") == name:
+                                return (type_node.nodeName, param)
+        return None
+        
+    def getParamsCategories(self):
+        """return the categories availables"""
+        categories=[]
+        for cat in self.dom.getElementsByTagName("category"):
+            categories.append(cat.getAttribute("name"))
+        return categories
+
+    def setParam(self, name, value, category, profile_key='@DEFAULT@'):
+        """Set a parameter, return None if the parameter is not in param xml"""
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_('Trying to set parameter for an unknown profile'))
+            return #TODO: throw an error
+
+        node = self.__getParamNode(name, category, '@ALL@')
+        if not node:
+            error(_('Requesting an unknown parameter (%(category)s/%(name)s)') % {'category':category, 'name':name})
+            return
+        
+        if node[0] == 'general':
+            self.params_gen[(category, name)] = value
+            self.host.bridge.paramUpdate(name, value, category, profile) #TODO: add profile in signal
+            return
+        
+        assert (node[0] == 'individual')
+        
+        type = node[1].getAttribute("type")
+        if type=="button":
+            print "clique",node.toxml()
+        else:
+            self.params[profile][(category, name)] = value
+            self.host.bridge.paramUpdate(name, value, category, profile) #TODO: add profile in signal
+
+class Memory:
+    """This class manage all persistent informations"""
+
+    def __init__(self, host):
+        info (_("Memory manager init"))
+        self.host = host
+        self.contacts={}
+        self.presenceStatus={}
+        self.subscriptions={}
+        self.params=Param(host)
+        self.history={}  #used to store chat history (key: short jid)
+        self.private={}  #used to store private value
+        host.set_const('savefile_history', SAVEFILE_HISTORY)
+        host.set_const('savefile_private', SAVEFILE_PRIVATE)
+        self.load()
+
+    def load(self):
+        """Load parameters and all memory things from file/db"""
+        param_file_xml = os.path.expanduser(self.host.get_const('local_dir')+
+                                        self.host.get_const('savefile_param_xml'))
+        param_file_data = os.path.expanduser(self.host.get_const('local_dir')+
+                                        self.host.get_const('savefile_param_data'))
+        history_file = os.path.expanduser(self.host.get_const('local_dir')+
+                                        self.host.get_const('savefile_history'))
+        private_file = os.path.expanduser(self.host.get_const('local_dir')+
+                                        self.host.get_const('savefile_private'))
+
+        #parameters
+        if os.path.exists(param_file_xml):
+            try:
+                self.params.load_xml(param_file_xml)
+                debug(_("params template loaded"))
+            except:
+                error (_("Can't load params template !"))
+                self.params.load_default_params()
+        else:
+            info (_("No params template, using default template"))
+            self.params.load_default_params()
+
+        try:
+            self.params.load_data(param_file_data)
+            debug(_("params loaded"))
+        except:
+            error (_("Can't load params !"))
+
+        #history
+        if os.path.exists(history_file):
+            try:
+                with open(history_file, 'r') as history_pickle:
+                    self.history=pickle.load(history_pickle)
+                debug(_("history loaded"))
+            except:
+                error (_("Can't load history !"))
+
+        #private
+        if os.path.exists(private_file):
+            try:
+                with open(private_file, 'r') as private_pickle:
+                    self.private=pickle.load(private_pickle)
+                debug(_("private values loaded"))
+            except:
+                error (_("Can't load private values !"))
+
+    def save(self):
+        """Save parameters and all memory things to file/db"""
+        #TODO: need to encrypt files (at least passwords !) and set permissions
+        param_file_xml = os.path.expanduser(self.host.get_const('local_dir')+
+                                        self.host.get_const('savefile_param_xml'))
+        param_file_data = os.path.expanduser(self.host.get_const('local_dir')+
+                                        self.host.get_const('savefile_param_data'))
+        history_file = os.path.expanduser(self.host.get_const('local_dir')+
+                                        self.host.get_const('savefile_history'))
+        private_file = os.path.expanduser(self.host.get_const('local_dir')+
+                                        self.host.get_const('savefile_private'))
+        
+        self.params.save_xml(param_file_xml)
+        self.params.save_data(param_file_data)
+        debug(_("params saved"))
+        with open(history_file, 'w') as history_pickle:
+            pickle.dump(self.history, history_pickle)
+        debug(_("history saved"))
+        with open(private_file, 'w') as private_pickle:
+            pickle.dump(self.private, private_pickle)
+        debug(_("private values saved"))
+
+    def getProfilesList(self):
+        return self.params.getProfilesList()
+
+
+    def getProfileName(self, profile_key):
+        """Return name of profile from keyword
+        @param profile_key: can be the profile name or a keywork (like @DEFAULT@)
+        @return: profile name or None if it doesn't exist"""
+        return self.params.getProfileName(profile_key)
+
+    def createProfile(self, name):
+        """Create a new profile
+        @param name: Profile name
+        """
+        return self.params.createProfile(name)
+    
+    def deleteProfile(self, name):
+        """Delete an existing profile
+        @param name: Name of the profile"""
+        return self.params.deleteProfile(name)
+
+    def addToHistory(self, me_jid, from_jid, to_jid, type, message):
+        me_short=me_jid.userhost()
+        from_short=from_jid.userhost()
+        to_short=to_jid.userhost()
+
+        if from_jid==me_jid:
+            key=to_short
+        else:
+            key=from_short
+
+        if not self.history.has_key(me_short):
+            self.history[me_short]={}
+        if not self.history[me_short].has_key(key):
+            self.history[me_short][key]={}
+
+        self.history[me_short][key][int(time.time())] = (from_jid.full(), message)
+        
+    def getHistory(self, from_jid, to_jid, size):
+        ret={}
+        if not self.history.has_key(from_jid):
+            error(_("source JID not found !"))
+            #TODO: throw an error here
+            return {}
+        if not self.history[from_jid].has_key(to_jid):
+            error(_("dest JID not found !"))
+            #TODO: throw an error here
+            return {}
+        stamps=self.history[from_jid][to_jid].keys()
+        stamps.sort()
+        for stamp in stamps[-size:]:
+            ret[stamp]=self.history[from_jid][to_jid][stamp]
+
+        return ret
+
+    def setPrivate(self, key, value):
+        """Save a misc private value (mainly useful for plugins)"""
+        self.private[key] = value
+
+    def getPrivate(self, key):
+        """return a private value
+        @param key: name of wanted value
+        @return: value or None if value don't exist"""
+        if self.private.has_key(key):
+            return self.private[key]
+        return None
+
+
+    def addContact(self, contact_jid, attributes, groups, profile_key='@DEFAULT@'):
+        debug("Memory addContact: %s",contact_jid.userhost())
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error (_('Trying to add a contact to a non-existant profile'))
+            return
+        assert(isinstance(attributes,dict))
+        assert(isinstance(groups,set))
+        if not self.contacts.has_key(profile):
+            self.contacts[profile] = {}
+        self.contacts[profile][contact_jid.userhost()]=[attributes, groups]
+
+    def delContact(self, contact_jid, profile_key='@DEFAULT@'):
+        debug("Memory delContact: %s",contact_jid.userhost())
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error (_('Trying to delete a contact for a non-existant profile'))
+            return
+        if self.contacts.has_key(profile) and self.contacts[profile].has_key(contact_jid.userhost()):
+            del self.contacts[profile][contact_jid.userhost()]
+    
+    def getContact(self, contact_jid, profile_key='@DEFAULT@'):
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_('Asking a contact for a non-existant profile'))
+            return None
+        if self.contacts.has_key(profile) and self.contacts[profile].has_key(contact_jid.userhost()):
+            self.contacts[profile][contact_jid.userhost()]
+        else:
+            return None
+    
+    def getContacts(self, profile_key='@DEFAULT@'):
+        """Return list of contacts for given profile
+        @param profile_key: profile key
+        @return list of [contact, attr, groups]"""
+        debug ("Memory getContact OK (%s)", self.contacts)
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_('Asking contacts for a non-existant profile'))
+            return []
+        ret=[]
+        for contact in self.contacts[profile]:
+            attr, groups = self.contacts[profile][contact]
+            ret.append([contact, attr, groups ])
+        return ret
+    
+    def addPresenceStatus(self, contact_jid, show, priority, statuses, profile_key='@DEFAULT@'):
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_('Trying to add presence status to a non-existant profile'))
+            return
+        if not self.presenceStatus.has_key(profile):
+            self.presenceStatus[profile] = {}
+        if not self.presenceStatus[profile].has_key(contact_jid.userhost()):
+            self.presenceStatus[profile][contact_jid.userhost()] = {}
+        resource = jid.parse(contact_jid.full())[2] or ''
+        self.presenceStatus[profile][contact_jid.userhost()][resource] = (show, priority, statuses)
+
+    def addWaitingSub(self, type, contact_jid, profile_key):
+        """Called when a subcription request is received"""
+        profile = self.getProfileName(profile_key)
+        assert(profile)
+        if not self.subscriptions.has_key(profile):
+            self.subscriptions[profile] = {}
+        self.subscriptions[profile][contact_jid] = type
+    
+    def delWaitingSub(self, contact_jid, profile_key):
+        """Called when a subcription request is finished"""
+        profile = self.getProfileName(profile_key)
+        assert(profile)
+        if self.subscriptions.has_key(profile) and self.subscriptions[profile].has_key(contact_jid):
+            del self.subscriptions[profile][contact_jid]
+    
+    def getWaitingSub(self, profile_key='@DEFAULT@'):
+        """Called to get a list of currently waiting subscription requests"""
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_('Asking waiting subscriptions for a non-existant profile'))
+            return {}
+        if not self.subscriptions.has_key(profile):
+            return {}
+        
+        return self.subscriptions[profile]
+
+    def getPresenceStatus(self, profile_key='@DEFAULT@'):
+        profile = self.getProfileName(profile_key)
+        if not profile:
+            error(_('Asking contacts for a non-existant profile'))
+            return {}
+        if not self.presenceStatus.has_key(profile):
+            self.presenceStatus[profile] = {}
+        debug ("Memory getPresenceStatus (%s)", self.presenceStatus[profile])
+        return self.presenceStatus[profile]
+
+    def getParamA(self, name, category, attr="value", profile_key="@DEFAULT@"):
+        return self.params.getParamA(name, category, attr, profile_key)
+    
+    def getParamsUI(self, profile_key='@DEFAULT@'):
+        return self.params.getParamsUI(profile_key)
+  
+    def getParams(self, profile_key='@DEFAULT@'):
+        return self.params.getParams(profile_key) 
+    
+    def getParamsForCategory(self, category, profile_key='@DEFAULT@'):
+        return self.params.getParamsForCategory(category, profile_key) 
+    
+    def getParamsCategories(self):
+        return self.params.getParamsCategories()
+    
+    def setParam(self, name, value, category, profile_key='@DEFAULT@'):
+        return self.params.setParam(name, value, category, profile_key)
+
+    def importParams(self, xml):
+        return self.params.importParams(xml)
+    
+    def setDefault(self, name, category, callback, errback=None):
+        return self.params.setDefault(name, category, callback, errback)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tools/xml_tools.py	Tue Jan 04 19:30:27 2011 +0100
@@ -0,0 +1,302 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+SAT: a jabber client
+Copyright (C) 2009, 2010  Jérôme Poisson (goffi@goffi.org)
+
+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, either version 3 of the License, 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from logging import debug, info, error
+from xml.dom import minidom
+from wokkel import data_form
+import pdb
+
+"""This library help manage XML used in SàT (parameters, registration, etc) """
+
+    
+def dataForm2xml(form):
+    """Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml"""
+    
+    form_ui = XMLUI("form", "vertical")
+
+    if form.instructions:
+        form_ui.addText('\n'.join(form.instructions), 'instructions')
+    
+    labels = filter(lambda field:field.label,form.fieldList)
+    if labels:
+        #if there is no label, we don't need to use pairs
+        form_ui.changeLayout("pairs")
+    
+    for field in form.fieldList:
+        if field.fieldType == 'fixed':
+            __field_type = 'text'
+        elif field.fieldType == 'text-single':
+            __field_type = "string"
+        elif field.fieldType == 'text-private':
+            __field_type = "password"
+        elif field.fieldType == 'list-single':
+            __field_type = "list"
+        else:
+            error (u"FIXME FIXME FIXME: Type [%s] is not managed yet by SàT" % field.fieldType)
+            __field_type = "string"
+        
+        if labels:
+            if field.label:
+                form_ui.addLabel(field.label)
+            else:
+                form_ui.addEmpty()
+
+        elem = form_ui.addElement(__field_type, field.var, field.value, [option.value for option in field.options]) 
+    return form_ui.toXml()
+
+def tupleList2dataForm(values):
+    """convert a list of tuples (name,value) to a wokkel submit data form"""
+    form = data_form.Form('submit')
+    for value in values:
+        field = data_form.Field(var=value[0], value=value[1])
+        form.addField(field)
+
+    return form
+
+def paramsXml2xmlUI(xml):
+    """Convert the xml for parameter to a SàT XML User Interface"""
+    params_doc = minidom.parseString(xml.encode('utf-8'))
+    top = params_doc.documentElement
+    if top.nodeName != 'params':
+        error(_('INTERNAL ERROR: parameters xml not valid'))
+        assert(False)
+    param_ui = XMLUI("param", "tabs")
+    for category in top.getElementsByTagName("category"):
+        name = category.getAttribute('name')
+        label = category.getAttribute('label')
+        if not name:
+            error(_('INTERNAL ERROR: params categories must have a name'))
+            assert(False)
+        param_ui.addCategory(name, 'pairs', label=label)
+        for param in category.getElementsByTagName("param"):
+            name = param.getAttribute('name')
+            label = param.getAttribute('label')
+            if not name:
+                error(_('INTERNAL ERROR: params must have a name'))
+                assert(False)
+            type = param.getAttribute('type')
+            value = param.getAttribute('value') or None
+            callback_id = param.getAttribute('callback_id') or None
+            if type == "button":
+                param_ui.addEmpty()
+            else:
+                param_ui.addLabel(label or name)
+            param_ui.addElement(name=name, type=type, value=value, callback_id=callback_id)
+
+    return param_ui.toXml()
+
+    
+
+
+class XMLUI:
+    """This class is used to create a user interface (form/window/parameters/etc) using SàT XML"""
+
+    def __init__(self, panel_type, layout="vertical", title=None):
+        """Init SàT XML Panel
+        @param panel_type: one of
+            - window (new window)
+            - form (formulaire, depend of the frontend, usually a panel with cancel/submit buttons)
+            - param (parameters, presentatio depend of the frontend)
+        @param layout: disposition of elements, one of:
+            - vertical: elements are disposed up to bottom
+            - horizontal: elements are disposed left to right
+            - pairs: elements come on two aligned columns
+              (usually one for a label, the next for the element)
+            - tabs: elemens are in categories with tabs (notebook)
+        @param title: title or default if None
+        """
+        if not panel_type in ['window', 'form', 'param']:
+            error(_("Unknown panel type [%s]") % panel_type)
+            assert(False)
+        self.type = panel_type
+        impl = minidom.getDOMImplementation()
+
+        self.doc = impl.createDocument(None, "sat_xmlui", None)
+        top_element = self.doc.documentElement
+        top_element.setAttribute("type", panel_type)
+        if title:
+            top_element.setAttribute("title", title)
+        self.parentTabsLayout = None #used only we have 'tabs' layout
+        self.currentCategory = None #used only we have 'tabs' layout
+        self.changeLayout(layout)
+
+    def __del__(self):
+        self.doc.unlink() 
+    
+    def __createLayout(self, layout, parent=None):
+        """Create a layout element
+        @param type: layout type (cf init doc)
+        @parent: parent element or None
+        """
+        if not layout in ['vertical', 'horizontal', 'pairs', 'tabs']:
+            error (_("Unknown layout type [%s]") % layout)
+            assert (False)
+        layout_elt = self.doc.createElement('layout')
+        layout_elt.setAttribute('type',layout)
+        if parent != None:
+            parent.appendChild(layout_elt)
+        return layout_elt
+
+    def __createElem(self, type, name=None, parent = None):
+        """Create an element
+        @param type: one of
+            - empty: empty element (usefull to skip something in a layout, e.g. skip first element in a PAIRS layout)
+            - text: text to be displayed in an multi-line area, e.g. instructions
+        @param name: name of the element or None
+        @param parent: parent element or None
+        """
+        elem = self.doc.createElement('elem')
+        if name:
+            elem.setAttribute('name', name)
+        elem.setAttribute('type', type)
+        if parent != None:
+            parent.appendChild(elem)
+        return elem
+
+    def changeLayout(self, layout):
+        """Change the current layout"""
+        self.currentLayout = self.__createLayout(layout, self.currentCategory if self.currentCategory else self.doc.documentElement)
+        if layout == "tabs":
+            self.parentTabsLayout = self.currentLayout
+
+
+    def addEmpty(self, name=None):
+        """Add a multi-lines text"""
+        elem = self.__createElem('empty', name, self.currentLayout)
+    
+    def addText(self, text, name=None):
+        """Add a multi-lines text"""
+        elem = self.__createElem('text', name, self.currentLayout)
+        text = self.doc.createTextNode(text)
+        elem.appendChild(text)
+
+    def addLabel(self, text, name=None):
+        """Add a single line text, mainly useful as label before element"""
+        elem = self.__createElem('label', name, self.currentLayout)
+        elem.setAttribute('value', text)
+
+    def addString(self, name=None, value=None):
+        """Add a string box"""
+        elem = self.__createElem('string', name, self.currentLayout)
+        if value:
+            elem.setAttribute('value', value)
+    
+    def addPassword(self, name=None, value=None):
+        """Add a password box"""
+        elem = self.__createElem('password', name, self.currentLayout)
+        if value:
+            elem.setAttribute('value', value)
+
+    def addTextBox(self, name=None, value=None):
+        """Add a string box"""
+        elem = self.__createElem('textbox', name, self.currentLayout)
+        if value:
+            elem.setAttribute('value', value)
+    
+    def addBool(self, name=None, value="true"):
+        """Add a string box"""
+        assert value in ["true","false"]
+        elem = self.__createElem('bool', name, self.currentLayout)
+        elem.setAttribute('value', value)
+    
+    def addList(self, options, name=None, value=None, style=set()):
+        """Add a list of choices"""
+        styles = set(style)
+        assert (options)
+        assert (styles.issubset(['multi'])) 
+        elem = self.__createElem('list', name, self.currentLayout)
+        self.addOptions(options, elem) 
+        if value:
+            elem.setAttribute('value', value)
+        for style in styles:
+            elem.setAttribute(style, 'yes')
+
+    def addButton(self, callback_id, name, value, fields_back=[]):
+        """Add a button
+        @param callback: callback which will be called if button is pressed
+        @param name: name
+        @param value: label of the button
+        @fields_back: list of names of field to give back when pushing the button"""
+        elem = self.__createElem('button', name, self.currentLayout)
+        elem.setAttribute('callback_id', callback_id)
+        elem.setAttribute('value', value)
+        for field in fields_back:
+            fback_el = self.doc.createElement('field_back')
+            fback_el.setAttribute('name', field)
+            elem.appendChild(fback_el)
+
+
+    
+    def addElement(self, type, name = None, value = None, options = None, callback_id = None):
+        """Convenience method to add element, the params correspond to the ones in addSomething methods"""
+        if type == 'empty':
+            self.addEmpty(name)
+        elif type == 'text':
+            assert(value!=None)
+            self.addText(value, name)
+        elif type == 'label':
+            assert(value)
+            self.addLabel(value)
+        elif type == 'string':
+            self.addString(name, value)
+        elif type == 'password':
+            self.addPassword(name, value)
+        elif type == 'textbox':
+            self.addTextBox(name, value)
+        elif type == 'bool':
+            if not value:
+                value = "true"
+            self.addBool(name, value)
+        elif type == 'list':
+            self.addList(options, name, value)
+        elif type == 'button':
+            assert(callback_id and value)
+            self.addButton(callback_id, name, value)
+
+    def addOptions(self, options, parent):
+        """Add options to a multi-values element (e.g. list)
+        @param parent: multi-values element"""
+        for option in options:
+            opt = self.doc.createElement('option')
+            opt.setAttribute('value', option)
+            parent.appendChild(opt)
+
+    def addCategory(self, name, layout, label=None):
+        """Add a category to current layout (must be a tabs layout)"""
+        assert(layout != 'tabs')
+        if not self.parentTabsLayout:
+            error(_("Trying to add a category without parent tabs layout"))
+            assert(False)
+        if self.parentTabsLayout.getAttribute('type') != 'tabs':
+            error(_("parent layout of a category is not tabs"))
+            assert(False)
+
+        if not label:
+            label = name
+        self.currentCategory = cat = self.doc.createElement('category')
+        cat.setAttribute('name', name)
+        cat.setAttribute('label', label)
+        self.changeLayout(layout)
+        self.parentTabsLayout.appendChild(cat)
+
+    def toXml(self):
+        """return the XML representation of the panel""" 
+        return self.doc.toxml()