quick_frontend, primitivus: multi-profiles refactoring part 1 (big commit, sorry :p):

This refactoring allow primitivus to manage correctly several profiles at once, with various other improvments:
    - profile_manager can now plug several profiles at once, requesting password when needed. No more profile plug specific method is used anymore in backend, instead a "validated" key is used in actions
    - Primitivus widget are now based on a common "PrimitivusWidget" classe which mainly manage the decoration so far
    - all widgets are treated in the same way (contactList, Chat, Progress, etc), no more chat_wins specific behaviour
    - widgets are created in a dedicated manager, with facilities to react on new widget creation or other events
    - quick_frontend introduce a new QuickWidget class, which aims to be as generic and flexible as possible. It can manage several targets (jids or something else), and several profiles
    - each widget class return a Hash according to its target. For example if given a target jid and a profile, a widget class return a hash like (target.bare, profile), the same widget will be used for all resources of the same jid
    - better management of CHAT_GROUP mode for Chat widgets
    - some code moved from Primitivus to QuickFrontend, the final goal is to have most non backend code in QuickFrontend, and just graphic code in subclasses
    - no more (un)escapePrivate/PRIVATE_PREFIX
    - contactList improved a lot: entities not in roster and special entities (private MUC conversations) are better managed
    - resources can be displayed in Primitivus, and their status messages
    - profiles are managed in QuickFrontend with dedicated managers


This is work in progress, other frontends are broken. Urwid SàText need to be updated. Most of features of Primitivus should work as before (or in a better way ;))
This commit is contained in:
Goffi 2014-12-10 19:00:09 +01:00
commit e4708c9cbb

View file

@ -21,32 +21,75 @@ from sat.core.i18n import _
from sat.core.log import getLogger
log = getLogger(__name__)
from sat_frontends.tools.jid import JID
from sat_frontends.quick_frontend.quick_utils import unescapePrivate
from sat_frontends.quick_frontend import quick_widgets
from sat_frontends.quick_frontend.constants import Const as C
class QuickChat(object):
class QuickChat(quick_widgets.QuickWidget):
def __init__(self, target, host, type_='one2one'):
self.target = target
self.host = host
def __init__(self, host, target, type_=C.CHAT_ONE2ONE, profiles=None):
"""
@param type_: can be C.CHAT_ONE2ONE for single conversation or C.CHAT_GROUP for chat à la IRC
"""
quick_widgets.QuickWidget.__init__(self, host, target, profiles=profiles)
assert type_ in (C.CHAT_ONE2ONE, C.CHAT_GROUP)
if type_ == C.CHAT_GROUP and target.resource:
raise ValueError("A group chat entity can't have a resource")
self.current_target = target
self.type = type_
self.id = ""
self.id = "" # FIXME: to be removed
self.nick = None
self.occupants = set()
def setType(self, type_):
"""Set the type of the chat
@param type: can be 'one2one' for single conversation or 'group' for chat à la IRC
def __str__(self):
return u"Chat Widget [target: {}, type: {}, profile: {}]".format(self.target, self.type, self.profile)
@staticmethod
def getWidgetHash(target, profile):
return (unicode(profile), target.bare)
@staticmethod
def getPrivateHash(target, profile):
"""Get unique hash for private conversations
This method should be used with force_hash to get unique widget for private MUC conversations
"""
self.type = type_
return (unicode(profile), target)
def addTarget(self, target):
super(QuickChat, self).addTarget(target)
if target.resource:
self.current_target = target # FIXME: tmp, must use resource priority throught contactList instead
@property
def target(self):
if self.type == C.CHAT_GROUP:
return self.current_target.bare
return self.current_target
def manageMessage(self, entity, mess_type):
"""Tell if this chat widget manage an entity and message type couple
@param entity (jid.JID): (full) jid of the sending entity
@param mess_type (str): message type as given by newMessage
@return (bool): True if this Chat Widget manage this couple
"""
if self.type == C.CHAT_GROUP:
if mess_type == C.MESS_TYPE_GROUPCHAT and self.target == entity.bare:
return True
else:
if mess_type != C.MESS_TYPE_GROUPCHAT and entity in self.targets:
return True
return False
def setPresents(self, nicks):
"""Set the users presents in the contact list for a group chat
@param nicks: list of nicknames
"""
log.debug (_("Adding users %s to room") % nicks)
if self.type != "group":
if self.type != C.CHAT_GROUP:
log.error (_("[INTERNAL] trying to set presents nicks for a non group chat window"))
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
self.occupants.update(nicks)
@ -54,7 +97,7 @@ class QuickChat(object):
def replaceUser(self, nick, show_info=True):
"""Add user if it is not in the group list"""
log.debug (_("Replacing user %s") % nick)
if self.type != "group":
if self.type != C.CHAT_GROUP:
log.error (_("[INTERNAL] trying to replace user for a non group chat window"))
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
len_before = len(self.occupants)
@ -65,7 +108,7 @@ class QuickChat(object):
def removeUser(self, nick, show_info=True):
"""Remove a user from the group list"""
log.debug(_("Removing user %s") % nick)
if self.type != "group":
if self.type != C.CHAT_GROUP:
log.error (_("[INTERNAL] trying to remove user for a non group chat window"))
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
self.occupants.remove(nick)
@ -82,7 +125,7 @@ class QuickChat(object):
def changeUserNick(self, old_nick, new_nick):
"""Change nick of a user in group list"""
log.debug(_("Changing nick of user %(old_nick)s to %(new_nick)s") % {"old_nick": old_nick, "new_nick": new_nick})
if self.type != "group":
if self.type != C.CHAT_GROUP:
log.error (_("[INTERNAL] trying to change user nick for a non group chat window"))
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
self.removeUser(old_nick, show_info=False)
@ -92,7 +135,7 @@ class QuickChat(object):
def setSubject(self, subject):
"""Set title for a group chat"""
log.debug(_("Setting subject to %s") % subject)
if self.type != "group":
if self.type != C.CHAT_GROUP:
log.error (_("[INTERNAL] trying to set subject for a non group chat window"))
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
@ -111,8 +154,8 @@ class QuickChat(object):
def onHistory(history):
for line in history:
timestamp, from_jid, to_jid, message, _type, extra = line
if ((self.type == 'group' and _type != 'groupchat') or
(self.type == 'one2one' and _type == 'groupchat')):
if ((self.type == C.CHAT_GROUP and _type != C.MESS_TYPE_GROUPCHAT) or
(self.type == C.CHAT_ONE2ONE and _type == C.MESS_TYPE_GROUPCHAT)):
continue
self.printMessage(JID(from_jid), message, profile, timestamp)
self.afterHistoryPrint()
@ -120,26 +163,47 @@ class QuickChat(object):
def onHistoryError(err):
log.error(_("Can't get history"))
if self.target.startswith(C.PRIVATE_PREFIX):
target = unescapePrivate(self.target)
target = self.target.bare
return self.host.bridge.getHistory(self.host.profiles[profile].whoami.bare, target, size, search=search, profile=profile, callback=onHistory, errback=onHistoryError)
def _get_nick(self, entity):
"""Return nick of this entity when possible"""
if self.type == C.CHAT_GROUP:
return entity.resource
contact_list = self.host.contact_lists[self.profile]
if entity.bare in contact_list:
return contact_list.getCache(entity,'nick') or contact_list.getCache(entity,'name') or entity.node or entity
return entity.node or entity
def onPrivateCreated(self, widget):
"""Method called when a new widget for private conversation (MUC) is created"""
raise NotImplementedError
def getOrCreatePrivateWidget(self, entity):
"""Create a widget for private conversation, or get it if it already exists
@param entity: full jid of the target
"""
return self.host.widgets.getOrCreateWidget(QuickChat, entity, type_=C.CHAT_ONE2ONE, force_hash=self.getPrivateHash(self.profile, entity), on_new_widget=self.onPrivateCreated, profile=self.profile) # we force hash to have a new widget, not this one again
def newMessage(self, from_jid, target, msg, type_, extra, profile):
if self.type == C.CHAT_GROUP and target.resource and type_ != C.MESS_TYPE_GROUPCHAT:
# we have a private message, we forward it to a private conversation widget
chat_widget = self.getOrCreatePrivateWidget(target)
chat_widget.newMessage(from_jid, target, msg, type_, extra, profile)
else:
target = self.target.bare
return self.host.bridge.getHistory(self.host.profiles[profile]['whoami'].bare, target, size, search=search, profile=profile, callback=onHistory, errback=onHistoryError)
def _get_nick(self, jid):
"""Return nick of this jid when possible"""
if self.target.startswith(C.PRIVATE_PREFIX):
unescaped = unescapePrivate(self.target)
if jid.startswith(C.PRIVATE_PREFIX) or unescaped.bare == jid.bare:
return unescaped.resource
return jid.resource if self.type == "group" else (self.host.contact_list.getCache(jid,'nick') or self.host.contact_list.getCache(jid,'name') or jid.node)
timestamp = extra.get('archive')
if type_ == C.MESS_TYPE_INFO:
self.printInfo(msg, timestamp=float(timestamp) if timestamp else None)
else:
self.printMessage(from_jid, msg, profile, float(timestamp) if timestamp else None)
def printMessage(self, from_jid, msg, profile, timestamp=None):
"""Print message in chat window. Must be implemented by child class"""
jid = JID(from_jid)
nick = self._get_nick(jid)
mymess = (jid.resource == self.nick) if self.type == "group" else (jid.bare == self.host.profiles[profile]['whoami'].bare) #mymess = True if message comes from local user
mymess = (jid.resource == self.nick) if self.type == C.CHAT_GROUP else (jid.bare == self.host.profiles[profile].whoami.bare) #mymess = True if message comes from local user
if msg.startswith('/me '):
self.printInfo('* %s %s' % (nick, msg[4:]), type_='me', timestamp=timestamp)
return
@ -165,12 +229,11 @@ class QuickChat(object):
#No need to raise an error as game are not mandatory
log.warning(_('getGame is not implemented in this frontend'))
def updateChatState(self, state, nick=None):
"""Set the chat state (XEP-0085) of the contact. Leave nick to None
to set the state for a one2one conversation, or give a nickname or
C.ALL_OCCUPANTS to set the state of a participant within a MUC.
def updateChatState(self, from_jid, state):
"""Set the chat state (XEP-0085) of the contact.
@param state: the new chat state
@param nick: None for one2one, the MUC user nick or ALL_OCCUPANTS
"""
raise NotImplementedError
quick_widgets.register(QuickChat)