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