# HG changeset patch # User Goffi # Date 1294165827 -3600 # Node ID 9c6ee3f9ab291a487b02a428cb20a37cc261db3d # Parent 86d249b6d9b771d735bd4bd8070f9021412eaa60 files reorganisation diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/__init__.py diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/bridge/DBus.py --- /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 . +""" + + +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): + 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+')', '','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")', '','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)) + diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/bridge/__init__.py diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/bridge/bridge.py --- /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 . +""" + +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 diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/sat/__init__.py diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/sat/bridge/DBus.py --- 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 . -""" - - -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): - 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+')', '','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")', '','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)) - diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/sat/bridge/__init__.py diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/sat/bridge/bridge.py --- 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 . -""" - -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 diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/sat/tools/__init__.py diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/sat/tools/games.py --- 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 . -""" - -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) diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/sat/tools/jid.py --- 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 . -""" - - - -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 diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/sat/tools/memory.py --- 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 . -""" - -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""" - - - - - - - - - - - - - - - - - - """ % {'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('') - - 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 "" - - 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) diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/sat/tools/xml_tools.py --- 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 . -""" - -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() diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/tools/__init__.py diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/tools/games.py --- /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 . +""" + +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) diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/tools/jid.py --- /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 . +""" + + + +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 diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/tools/memory.py --- /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 . +""" + +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""" + + + + + + + + + + + + + + + + + + """ % {'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('') + + 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 "" + + 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) diff -r 86d249b6d9b7 -r 9c6ee3f9ab29 src/tools/xml_tools.py --- /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 . +""" + +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()