frontends, core, plugin XEP-0045: leave implementation + better nick change

- memory: individual entity cache can be deleted
	- plugin XEP-0045: nick change are now detected and userChangedNick signal is sent instead of joined/left
	- plugin XEP-0045: leave implementation
	- frontends: userChangedNick signal management
	- Primitivus: an alert is shown in notification bar in case of error in sendMessage
This commit is contained in:
Goffi 2012-09-28 00:26:24 +02:00
commit c520325941
8 changed files with 145 additions and 50 deletions

View file

@ -203,10 +203,10 @@ class Chat(urwid.WidgetWrap, QuickChat):
self.present_wid.changeValues(nicks)
self.host.redraw()
def replaceUser(self, param_nick):
def replaceUser(self, param_nick, show_info=True):
"""Add user if it is not in the group list"""
nick = unicode(param_nick) #FIXME: should be done in DBus bridge
QuickChat.replaceUser(self, nick)
QuickChat.replaceUser(self, nick, show_info)
presents = self.present_wid.getAllValues()
if nick not in presents:
presents.append(nick)
@ -214,10 +214,10 @@ class Chat(urwid.WidgetWrap, QuickChat):
self.present_wid.changeValues(presents)
self.host.redraw()
def removeUser(self, param_nick):
def removeUser(self, param_nick, show_info=True):
"""Remove a user from the group list"""
nick = unicode(param_nick) #FIXME: should be done in DBus bridge
QuickChat.removeUser(self, nick)
QuickChat.removeUser(self, nick, show_info)
self.present_wid.deleteValue(nick)
self.host.redraw()
@ -234,11 +234,11 @@ class Chat(urwid.WidgetWrap, QuickChat):
#as that mean that he is probably watching discussion history
self.text_list.set_focus(len(self.content)-1)
self.host.redraw()
if not self.host.notify.hasFocus():
if not self.host.x_notify.hasFocus():
if self.type=="one2one":
self.host.notify.sendNotification(_("Primitivus: %s is talking to you") % from_jid)
self.host.x_notify.sendNotification(_("Primitivus: %s is talking to you") % from_jid)
elif self.getUserNick().lower() in msg.lower():
self.host.notify.sendNotification(_("Primitivus: Somebody pinged your name in %s room") % self.target)
self.host.x_notify.sendNotification(_("Primitivus: Somebody pinged your name in %s room") % self.target)
def printInfo(self, msg, type='normal'):
"""Print general info
@ -253,11 +253,11 @@ class Chat(urwid.WidgetWrap, QuickChat):
#as that mean that he is probably watching discussion history
self.text_list.set_focus(len(self.content)-1)
self.host.redraw()
if not self.host.notify.hasFocus():
if not self.host.x_notify.hasFocus():
if self.type=="one2one":
self.host.notify.sendNotification(_("Primitivus: there is a message about you"))
self.host.x_notify.sendNotification(_("Primitivus: there is a message about you"))
elif self.getUserNick().lower() in msg.lower():
self.host.notify.sendNotification(_("Primitivus: Somebody is talking about you in %s room") % self.target)
self.host.x_notify.sendNotification(_("Primitivus: Somebody is talking about you in %s room") % self.target)
def startGame(self, game_type, referee, players):
"""Configure the chat window to start a game"""

View file

@ -239,6 +239,10 @@ class ContactList(urwid.WidgetWrap, QuickContactList):
groups_to_remove.append(group)
for group in groups_to_remove:
del self.groups[group]
try:
del self.special[jid.short]
except KeyError:
pass
self.update()
def add(self, jid, param_groups=[None]):

View file

@ -70,7 +70,7 @@ class PrimitivusApp(QuickApp):
urwid.connect_signal(self.notBar.progress,'click',lambda x:self.addWindow(self.progress_wid))
self.__saved_overlay = None
self.notify = Notify()
self.x_notify = Notify()
def debug(self):
"""convenient method to reset screen and launch p(u)db"""
@ -299,10 +299,13 @@ class PrimitivusApp(QuickApp):
contact = self.contact_list.getContact() ###Based on the fact that there is currently only one contact selectableat once
if contact:
chat = self.chat_wins[contact]
self.bridge.sendMessage(contact,
try:
self.bridge.sendMessage(contact,
editBar.get_edit_text(),
mess_type = "groupchat" if chat.type == 'group' else "chat",
profile_key=self.profile)
except:
self.notify(_("Error while sending message"))
editBar.set_edit_text('')
def newMessage(self, from_jid, msg, type, to_jid, profile):

View file

@ -57,8 +57,10 @@ class QuickApp():
self.bridge.register("actionResult", self.actionResult)
self.bridge.register("actionResultExt", self.actionResult)
self.bridge.register("roomJoined", self.roomJoined, "plugin")
self.bridge.register("roomLeft", self.roomLeft, "plugin")
self.bridge.register("roomUserJoined", self.roomUserJoined, "plugin")
self.bridge.register("roomUserLeft", self.roomUserLeft, "plugin")
self.bridge.register("roomUserChangedNick", self.roomUserChangedNick, "plugin")
self.bridge.register("roomNewSubject", self.roomNewSubject, "plugin")
self.bridge.register("tarotGameStarted", self.tarotGameStarted, "plugin")
self.bridge.register("tarotGameNew", self.tarotGameNew, "plugin")
@ -290,6 +292,13 @@ class QuickApp():
self.chat_wins[room_jid].setPresents(list(set([user_nick]+room_nicks)))
self.contact_list.setSpecial(JID(room_jid), "MUC")
def roomLeft(self, room_jid, profile):
"""Called when a MUC room is left"""
if not self.check_profile(profile):
return
debug (_("Room [%(room_jid)s] left by %(profile)s") % {'room_jid':room_jid, 'profile': profile})
del self.chat_wins[room_jid]
self.contact_list.remove(room_jid)
def roomUserJoined(self, room_jid, user_nick, user_data, profile):
"""Called when an user joined a MUC room"""
@ -307,6 +316,14 @@ class QuickApp():
self.chat_wins[room_jid].removeUser(user_nick)
debug (_("user [%(user_nick)s] left room [%(room_jid)s]") % {'user_nick':user_nick, 'room_jid':room_jid})
def roomUserChangedNick(self, room_jid, old_nick, new_nick, profile):
"""Called when an user joined a MUC room"""
if not self.check_profile(profile):
return
if self.chat_wins.has_key(room_jid):
self.chat_wins[room_jid].changeUserNick(old_nick, new_nick)
debug (_("user [%(old_nick)s] is now known as [%(new_nick)s] in room [%(room_jid)s]") % {'old_nick':old_nick, 'new_nick':new_nick, 'room_jid':room_jid})
def roomNewSubject(self, room_jid, subject, profile):
"""Called when subject of MUC room change"""
if not self.check_profile(profile):

View file

@ -50,7 +50,7 @@ class QuickChat():
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
self.occupants.update(nicks)
def replaceUser(self, nick):
def replaceUser(self, nick, show_info=True):
"""Add user if it is not in the group list"""
debug (_("Replacing user %s") % nick)
if self.type != "group":
@ -58,24 +58,35 @@ class QuickChat():
raise Exception("INTERNAL ERROR") #TODO: raise proper Exception here
len_before = len(self.occupants)
self.occupants.add(nick)
if len_before != len(self.occupants):
if len_before != len(self.occupants) and show_info:
self.printInfo("=> %s has joined the room" % nick)
def setUserNick(self, nick):
"""Set the nick of the user, usefull for e.g. change the color of the user"""
self.nick = nick
def getUserNick(self):
return unicode(self.nick)
def removeUser(self, nick):
def removeUser(self, nick, show_info=True):
"""Remove a user from the group list"""
debug(_("Removing user %s") % nick)
if self.type != "group":
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)
self.printInfo("<= %s has left the room" % nick)
if show_info:
self.printInfo("<= %s has left the room" % nick)
def setUserNick(self, nick):
"""Set the nick of the user, usefull for e.g. change the color of the user"""
self.nick = nick
def getUserNick(self):
return unicode(self.nick)
def changeUserNick(self, old_nick, new_nick):
"""Change nick of a user in group list"""
debug(_("Changing nick of user %(old_nick)s to %(new_nick)s") % {"old_nick": old_nick, "new_nick": new_nick})
if self.type != "group":
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)
self.replaceUser(new_nick, show_info=False)
self.printInfo("%s is now known as %s" % (old_nick, new_nick))
def setSubject(self, subject):
"""Set title for a group chat"""

View file

@ -142,18 +142,18 @@ class Chat(wx.Frame, QuickChat):
for nick in nicks:
self.present_panel.presents.replace(nick)
def replaceUser(self, nick):
def replaceUser(self, nick, show_info=True):
"""Add user if it is not in the group list"""
debug (_("Replacing user %s") % nick)
if self.type != "group":
error (_("[INTERNAL] trying to replace user for a non group chat window"))
return
QuickChat.replaceUser(self, nick)
QuickChat.replaceUser(self, nick, show_info)
self.present_panel.presents.replace(nick)
def removeUser(self, nick):
def removeUser(self, nick, show_info=True):
"""Remove a user from the group list"""
QuickChat.removeUser(self, nick)
QuickChat.removeUser(self, nick, show_info)
self.present_panel.presents.remove(nick)
def setSubject(self, subject):

View file

@ -750,6 +750,18 @@ class Memory:
ret[key] = entity_data[key]
return ret
def delEntityCache(self, entity_jid, profile_key):
"""Remove cached data for entity
@param entity_jid: JID of the entity
"""
profile = self.getProfileName(profile_key)
try:
del self.entitiesCache[profile][entity_jid.userhost()]
except KeyError:
pass
def addWaitingSub(self, _type, entity_jid, profile_key):
"""Called when a subcription request is received"""
profile = self.getProfileName(profile_key)

View file

@ -20,24 +20,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from logging import debug, info, warning, error
from twisted.words.xish import domish
from twisted.internet import protocol, defer, threads, reactor
from twisted.words.protocols.jabber import client, jid, xmlstream
from twisted.words.protocols.jabber import error as jab_error
from twisted.words.protocols.jabber.xmlstream import IQ
from twisted.internet import defer
from twisted.words.protocols.jabber import jid
from sat.core import exceptions
import os.path
import uuid
from zope.interface import implements
from wokkel import muc
from wokkel import disco, iwokkel, muc
from base64 import b64decode
from hashlib import sha1
from time import sleep
try:
from twisted.words.protocols.xmlstream import XMPPHandler
@ -65,15 +56,19 @@ class XEP_0045():
self.host = host
self.clients={}
host.bridge.addMethod("joinMUC", ".plugin", in_sign='ssa{ss}s', out_sign='', method=self._join)
host.bridge.addMethod("changeNick", ".plugin", in_sign='sss', out_sign='', method=self.changeNick)
host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self.mucNick)
host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self.leave)
host.bridge.addMethod("getRoomsJoined", ".plugin", in_sign='s', out_sign='a(sass)', method=self.getRoomsJoined)
host.bridge.addMethod("getRoomsSubjects", ".plugin", in_sign='s', out_sign='a(ss)', method=self.getRoomsSubjects)
host.bridge.addMethod("getUniqueRoomName", ".plugin", in_sign='s', out_sign='s', method=self.getUniqueName)
host.bridge.addSignal("roomJoined", ".plugin", signature='sasss') #args: room_jid, room_nicks, user_nick, profile
host.bridge.addSignal("roomLeft", ".plugin", signature='ss') #args: room_jid, profile
host.bridge.addSignal("roomUserJoined", ".plugin", signature='ssa{ss}s') #args: room_jid, user_nick, user_data, profile
host.bridge.addSignal("roomUserLeft", ".plugin", signature='ssa{ss}s') #args: room_jid, user_nick, user_data, profile
host.bridge.addSignal("roomUserChangedNick", ".plugin", signature='ssss') #args: room_jid, old_nick, new_nick, profile
host.bridge.addSignal("roomNewSubject", ".plugin", signature='sss') #args: room_jid, subject, profile
def __check_profile(self, profile):
"""check if profile is used and connected
if profile known but disconnected, remove it from known profiles
@ -179,18 +174,30 @@ class XEP_0045():
d = self.join(room_jid, nick, options, profile)
d.addErrback(lambda x: warning(_('Error while joining room'))) #TODO: error management + signal in bridge
def nick(self, room_jid, nick, profile_key='@DEFAULT@'):
def nick(self, room_jid, nick, profile_key):
profile = self.host.memory.getProfileName(profile_key)
if not self.__check_profile(profile):
raise exceptions.UnknownProfileError("Unknown or disconnected profile")
if not self.clients[profile].joined_rooms.has_key(room_jid.userhost()):
raise UnknownRoom("This room has not been joined")
return self.clients[profile].nick(room_jid, nick)
def changeNick(self, room_jid_s, nick, profile_key='@DEFAULT@'):
def leave(self, room_jid, profile_key):
profile = self.host.memory.getProfileName(profile_key)
if not self.__check_profile(profile):
raise exceptions.UnknownProfileError("Unknown or disconnected profile")
if not self.clients[profile].joined_rooms.has_key(room_jid.userhost()):
raise UnknownRoom("This room has not been joined")
return self.clients[profile].leave(room_jid)
def mucNick(self, room_jid_s, nick, profile_key='@DEFAULT@'):
"""Change nickname in a room"""
return self.nick(jid.JID(room_jid_s), nick, profile_key)
def mucLeave(self, room_jid_s, profile_key='@DEFAULT@'):
"""Leave a room"""
return self.leave(jid.JID(room_jid_s), profile_key)
def getHandler(self, profile):
self.clients[profile] = SatMUCClient(self)
return self.clients[profile]
@ -206,22 +213,62 @@ class SatMUCClient (muc.MUCClient):
muc.MUCClient.__init__(self)
self.joined_rooms = {}
self.rec_subjects = {}
self.__changing_nicks = set() # used to keep trace of who is changing nick,
# and to discard userJoinedRoom signal in this case
print "init SatMUCClient OK"
def unavailableReceived(self, presence):
#XXX: we override this method to manage nickname change
#TODO: feed this back to Wokkel
"""
Unavailable presence was received.
If this was received from a MUC room occupant JID, that occupant has
left the room.
"""
room, user = self._getRoomUser(presence)
if room is None or user is None:
return
room.removeUser(user)
if muc.STATUS_CODE.NEW_NICK in presence.mucStatuses:
self.__changing_nicks.add(presence.nick)
self.userChangedNick(room, user, presence.nick)
else:
self.__changing_nicks.discard(presence.nick)
self.userLeftRoom(room, user)
def receivedGroupChat(self, room, user, body):
debug('receivedGroupChat: room=%s user=%s body=%s', room, user, body)
def userJoinedRoom(self, room, user):
debug (_("user %(nick)s has joined room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile):
return
user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role}
self.host.bridge.roomUserJoined(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
if user.nick in self.__changing_nicks:
self.__changing_nicks.remove(user.nick)
else:
debug (_("user %(nick)s has joined room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile):
return
user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role}
self.host.bridge.roomUserJoined(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
def userLeftRoom(self, room, user):
debug (_("user %(nick)s left room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role}
self.host.bridge.roomUserLeft(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
if user.nick == room.nick:
# we left the room
room_jid_s = room.roomJID.userhost()
info (_("Room [%(room)s] left (%(profile)s))") % { "room": room_jid_s,
"profile": self.parent.profile })
self.host.memory.delEntityCache(room.roomJID, self.parent.profile)
del self.plugin_parent.clients[self.parent.profile].joined_rooms[room_jid_s]
self.host.bridge.roomLeft(room.roomJID.userhost(), self.parent.profile)
else:
debug (_("user %(nick)s left room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
user_data={'entity':user.entity.full() if user.entity else '', 'affiliation':user.affiliation, 'role':user.role}
self.host.bridge.roomUserLeft(room.roomJID.userhost(), user.nick, user_data, self.parent.profile)
def userChangedNick(self, room, user, new_nick):
self.host.bridge.roomUserChangedNick(room.roomJID.userhost(), user.nick, new_nick, self.parent.profile)
def userUpdatedStatus(self, room, user, show, status):
print("FIXME: MUC status not managed yet")
@ -232,6 +279,7 @@ class SatMUCClient (muc.MUCClient):
self.rec_subjects[room.roomJID.userhost()] = (room.roomJID.userhost(), subject)
self.host.bridge.roomNewSubject(room.roomJID.userhost(), subject, self.parent.profile)
#def connectionInitialized(self):
#pass