changeset 510:886754295efe

quick frontend, primitivus, wix: MUC private messages management /!\ not fully finished, backend part is not done yet /!\ - as resources are discarded to manage chat windows lists, a pretty dirty hack is done to work around this: full jid is escaped using a prefix (it becomes invalid and resource is preserved). - new quick_utils module, with helper methods. escapePrivate and unescapePrivate implementations - MUC private messages are not managed in Wix yet
author Goffi <goffi@goffi.org>
date Thu, 11 Oct 2012 00:48:35 +0200
parents 64ff046dc201
children 62f7f2403093
files frontends/src/primitivus/chat.py frontends/src/primitivus/contact_list.py frontends/src/primitivus/primitivus frontends/src/quick_frontend/constants.py frontends/src/quick_frontend/quick_app.py frontends/src/quick_frontend/quick_chat.py frontends/src/quick_frontend/quick_contact_list.py frontends/src/quick_frontend/quick_utils.py frontends/src/wix/chat.py frontends/src/wix/contact_list.py frontends/src/wix/main_window.py
diffstat 11 files changed, 163 insertions(+), 77 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/primitivus/chat.py	Fri Sep 28 00:48:52 2012 +0200
+++ b/frontends/src/primitivus/chat.py	Thu Oct 11 00:48:35 2012 +0200
@@ -22,9 +22,9 @@
 import urwid
 from urwid_satext import sat_widgets
 from urwid_satext.files_management import FileDialog
-from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
 from sat_frontends.quick_frontend.quick_chat import QuickChat
 from sat_frontends.primitivus.card_game import CardGame
+from sat_frontends.quick_frontend.quick_utils import unescapePrivate
 import time
 from sat.tools.jid  import JID
 
@@ -145,7 +145,7 @@
                 self.__appendPresentPanel()
           
     def __getDecoration(self, widget):
-        return sat_widgets.LabelLine(widget, sat_widgets.SurroundedText(unicode(self.target)))
+        return sat_widgets.LabelLine(widget, sat_widgets.SurroundedText(unicode(unescapePrivate(self.target))))
 
     def showDecoration(self, show=True):
         """Show/Hide the decoration around the chat window"""
--- a/frontends/src/primitivus/contact_list.py	Fri Sep 28 00:48:52 2012 +0200
+++ b/frontends/src/primitivus/contact_list.py	Thu Oct 11 00:48:35 2012 +0200
@@ -22,6 +22,7 @@
 import urwid
 from urwid_satext import sat_widgets
 from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
+from sat_frontends.quick_frontend.quick_utils import escapePrivate, unescapePrivate
 from sat.tools.jid import JID
 
 
@@ -29,10 +30,10 @@
     signals = ['click','change']
 
     def __init__(self, host, on_click=None, on_change=None, user_data=None):
+        QuickContactList.__init__(self)
         self.host = host
         self.selected = None
         self.groups={}
-        self.special={}
         self.alert_jid=set()
         self.show_status = False
         self.show_disconnected = False
@@ -45,7 +46,6 @@
             urwid.connect_signal(self, 'click', on_click, user_data)
         if on_change:
             urwid.connect_signal(self, 'change', on_change, user_data)
-        QuickContactList.__init__(self)
 
     def update(self):
         """Update display, keep focus"""
@@ -115,18 +115,23 @@
         widgets = [] #list of built widgets
         
         for contact in contacts:
-            jid=JID(contact) 
-            name = self.getCache(jid, 'name')
-            nick = self.getCache(jid, 'nick')
-            status = self.getCache(jid, 'status')
-            show = self.getCache(jid, 'show')
-            if show == None:
-                show = "unavailable"
-            if (not self.show_disconnected and show == "unavailable"
-                and not contact in self.alert_jid and contact != self.selected):
-                continue
-            show_icon, show_attr = const_SHOW_ICON.get(show,('','default'))
-            contact_disp = ('alert' if contact in self.alert_jid else show_attr, nick or name or jid.node or jid.short)
+            if contact.startswith(const_PRIVATE_PREFIX):
+                contact_disp = ('alert' if contact in self.alert_jid else "show_normal", unescapePrivate(contact))
+                show_icon = ''
+                status = ''
+            else:
+                jid=JID(contact)
+                name = self.getCache(jid, 'name')
+                nick = self.getCache(jid, 'nick')
+                status = self.getCache(jid, 'status')
+                show = self.getCache(jid, 'show')
+                if show == None:
+                    show = "unavailable"
+                if (not self.show_disconnected and show == "unavailable"
+                    and not contact in self.alert_jid and contact != self.selected):
+                    continue
+                show_icon, show_attr = const_SHOW_ICON.get(show,('','default'))
+                contact_disp = ('alert' if contact in self.alert_jid else show_attr, nick or name or jid.node or jid.short)
             display = [ show_icon + " " , contact_disp]
             if self.show_status:
                 status_disp = ('status',"\n  " + status) if status else ""
@@ -147,7 +152,7 @@
 
     def __buildSpecials(self, content):
         """Build the special entities"""
-        specials = self.special.keys()
+        specials = self.specials.keys()
         specials.sort()
         for special in specials:
             jid=JID(special) 
@@ -168,7 +173,7 @@
         content = urwid.SimpleListWalker([])
         
         self.__buildSpecials(content)
-        if self.special:
+        if self.specials:
             content.append(urwid.Divider('='))
 
         group_keys = self.groups.keys()
@@ -198,6 +203,7 @@
             
     def clearContacts(self):
         """clear all the contact list"""
+        QuickContactList.clearContacts(self)
         self.groups={}
         self.selected = None
         self.unselectAll()
@@ -205,7 +211,8 @@
 
     def replace(self, jid, groups=None, attributes=None):
         """add a contact to the list if doesn't exist, else update it"""
-        if jid.short in self.special:
+        QuickContactList.replace(self, jid, groups, attributes)
+        if jid.short in self.specials:
             return
         if not groups:
             groups = [None]
@@ -227,10 +234,10 @@
             self.list_wid.changeValues(contacts)
             self._emit('change')"""
     
-    def remove(self, param_jid):
+    def remove(self, jid):
         """remove a contact from the list"""
+        QuickContactList.remove(self, jid)
         groups_to_remove = []
-        jid = JID(param_jid)
         for group in self.groups:
             contacts = self.groups[group][1]
             if jid.short in contacts:
@@ -239,10 +246,6 @@
                     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]):
@@ -254,7 +257,7 @@
         @param jid: jid of the entity
         @param _type: special type (e.g.: "MUC")
         """
-        self.special[special_jid.short] = special_type
+        QuickContactList.setSpecial(self, special_jid, special_type)
         if None in self.groups:
             folded,group_jids = self.groups[None]
             for group_jid in group_jids:
@@ -265,5 +268,5 @@
 
     def updatePresence(self, jid, show, priority, statuses):
         #XXX: for the moment, we ignore presence updates for special entities
-        if jid.short not in self.special:
+        if jid.short not in self.specials:
             QuickContactList.updatePresence(self, jid, show, priority, statuses)
--- a/frontends/src/primitivus/primitivus	Fri Sep 28 00:48:52 2012 +0200
+++ b/frontends/src/primitivus/primitivus	Thu Oct 11 00:48:35 2012 +0200
@@ -24,8 +24,6 @@
 from urwid_satext import sat_widgets
 from sat_frontends.quick_frontend.quick_app import QuickApp
 from sat_frontends.quick_frontend.quick_chat_list import QuickChatList
-from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
-from sat_frontends.quick_frontend.quick_contact_management import QuickContactManagement
 from sat_frontends.primitivus.profile_manager import ProfileManager
 from sat_frontends.primitivus.contact_list import ContactList
 from sat_frontends.primitivus.chat import Chat
@@ -47,9 +45,6 @@
 class ChatList(QuickChatList):
     """This class manage the list of chat windows"""
     
-    def __init__(self, host):
-        QuickChatList.__init__(self, host)
-    
     def createChat(self, target):
         return Chat(target, self.host)
 
@@ -300,7 +295,7 @@
         if contact:
             chat = self.chat_wins[contact]
             try:
-                self.bridge.sendMessage(contact,
+                self.sendMessage(contact,
                                     editBar.get_edit_text(),
                                     mess_type = "groupchat" if chat.type == 'group' else "chat",
                                     profile_key=self.profile)
@@ -308,18 +303,17 @@
                 self.notify(_("Error while sending message"))
             editBar.set_edit_text('')
 
-    def newMessage(self, from_jid, msg, type, to_jid, profile):
-        if not self.check_profile(profile):
-            return
-        sender = JID(from_jid)
-        if not sender in self.contact_list and sender.short != self.profiles[profile]['whoami'].short:
+    def newMessage(self, from_jid, to_jid, msg, _type, profile):
+        QuickApp.newMessage(self, from_jid, to_jid, msg, _type, profile)
+        
+        if not from_jid in self.contact_list and from_jid.short != self.profiles[profile]['whoami'].short:
             #XXX: needed to show entities which haven't sent any
             #     presence information and which are not in roster
             #TODO: put these entities in a "not in roster" list
-            self.contact_list.replace(sender)
-        QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
-        if JID(self.contact_list.selected).short != sender.short:
-            self.contact_list.putAlert(sender)
+            self.contact_list.replace(from_jid)
+        
+        if JID(self.contact_list.selected).short != from_jid.short:
+            self.contact_list.putAlert(from_jid)
 
     def _dialogOkCb(self, widget, data):
         self.removePopUp()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/src/quick_frontend/constants.py	Thu Oct 11 00:48:35 2012 +0200
@@ -0,0 +1,23 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Primitivus: a SAT frontend
+Copyright (C) 2009, 2010, 2011, 2012  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 Affero 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+import __builtin__
+
+__builtin__.__dict__['const_PRIVATE_PREFIX'] = "@PRIVATE@"
--- a/frontends/src/quick_frontend/quick_app.py	Fri Sep 28 00:48:52 2012 +0200
+++ b/frontends/src/quick_frontend/quick_app.py	Thu Oct 11 00:48:35 2012 +0200
@@ -22,8 +22,11 @@
 from logging import debug, info, warning, error
 from sat.tools.jid  import JID
 from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService
+from sat_frontends.quick_frontend.quick_utils import escapePrivate, unescapePrivate
 from optparse import OptionParser
 
+import sat_frontends.quick_frontend.constants
+
 import gettext
 gettext.install('sat_frontend', "../i18n", unicode=True)
 
@@ -46,7 +49,7 @@
         self.bridge.register("disconnected", self.disconnected)
         self.bridge.register("connectionError", self.connectionError)
         self.bridge.register("newContact", self.newContact)
-        self.bridge.register("newMessage", self.newMessage)
+        self.bridge.register("newMessage", self._newMessage)
         self.bridge.register("newAlert", self.newAlert)
         self.bridge.register("presenceUpdate", self.presenceUpdate)
         self.bridge.register("subscribe", self.subscribe)
@@ -230,17 +233,48 @@
             return
         entity=JID(JabberId)
         _groups = list(groups)
-        self.contact_list._replace(entity, _groups, attributes)
-    
-    def newMessage(self, from_jid, msg, type, to_jid, profile):
+        self.contact_list.replace(entity, _groups, attributes)
+
+    def _newMessage(self, from_jid_s, msg, _type, to_jid_s, profile):
+        """newMessage premanagement: a dirty hack to manage private messages
+        if a private MUC message is detected, from_jid or to_jid is prefixed and resource is escaped"""
         if not self.check_profile(profile):
             return
-        sender=JID(from_jid)
-        addr=JID(to_jid)
-        win = addr if sender.short == self.profiles[profile]['whoami'].short else sender
+        from_jid = JID(from_jid_s)
+        to_jid = JID(to_jid_s)
+        
+        from_me = from_jid.short == self.profiles[profile]['whoami'].short
+        win = to_jid if from_me else from_jid
+        
+        if _type != "groupchat" and self.contact_list.getSpecial(win) == "MUC":
+            #we have a private message in a MUC room
+            #XXX: normaly we use bare jid as key, here we need the full jid
+            #     so we cheat by replacing the "/" before the resource by
+            #     a "@", so the jid is invalid, 
+            new_jid = escapePrivate(win)
+            if from_me:
+                to_jid = new_jid
+            else:
+                from_jid = new_jid
+            if new_jid not in self.contact_list:
+                self.contact_list.add(new_jid)
+
+        self.newMessage(from_jid, to_jid, msg, _type, profile)
+    
+    def newMessage(self, from_jid, to_jid, msg, _type, profile):
+        from_me = from_jid.short == self.profiles[profile]['whoami'].short
+        win = to_jid if from_me else from_jid
+        
         self.current_action_ids = set()
         self.current_action_ids_cb = {}
-        self.chat_wins[win.short].printMessage(sender, msg, profile)
+
+        self.chat_wins[win.short].printMessage(from_jid, msg, profile)
+
+    def sendMessage(self, to_jid, message, subject='', mess_type="auto", profile_key="@DEFAULT@"):
+        if to_jid.startswith(const_PRIVATE_PREFIX):
+            to_jid = unescapePrivate(to_jid)
+            mess_type = "chat"
+        self.bridge.sendMessage(to_jid, message, subject, mess_type, profile_key)
 
     def newAlert(self, msg, title, alert_type, profile):
         if not self.check_profile(profile):
@@ -292,13 +326,13 @@
         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):
+    def roomLeft(self, room_jid_s, 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)
+        debug (_("Room [%(room_jid)s] left by %(profile)s") % {'room_jid':room_jid_s, 'profile': profile})
+        del self.chat_wins[room_jid_s]
+        self.contact_list.remove(JID(room_jid_s))
 
     def roomUserJoined(self, room_jid, user_nick, user_data, profile):
         """Called when an user joined a MUC room"""
@@ -504,12 +538,12 @@
         if key == "nick":
             if jid in self.contact_list:
                 self.contact_list.setCache(jid, 'nick', value)
-                self.contact_list._replace(jid)
+                self.contact_list.replace(jid)
         elif key == "avatar":
             if jid in self.contact_list:
                 filename = self.bridge.getAvatarFile(value)
                 self.contact_list.setCache(jid, 'avatar', filename)
-                self.contact_list._replace(jid)
+                self.contact_list.replace(jid)
 
     def askConfirmation(self, type, id, data):
         raise NotImplementedError
--- a/frontends/src/quick_frontend/quick_chat.py	Fri Sep 28 00:48:52 2012 +0200
+++ b/frontends/src/quick_frontend/quick_chat.py	Thu Oct 11 00:48:35 2012 +0200
@@ -21,7 +21,7 @@
 
 from logging import debug, info, warning, error
 from sat.tools.jid  import JID
-
+from sat_frontends.quick_frontend.quick_utils import unescapePrivate
 
 
 class QuickChat():
@@ -110,6 +110,8 @@
 
     def _get_nick(self, jid):
         """Return nick of this jid when possible"""
+        if jid.startswith(const_PRIVATE_PREFIX):
+            return unescapePrivate(jid).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)
     
     def printMessage(self, from_jid, msg, profile, timestamp):
--- a/frontends/src/quick_frontend/quick_contact_list.py	Fri Sep 28 00:48:52 2012 +0200
+++ b/frontends/src/quick_frontend/quick_contact_list.py	Thu Oct 11 00:48:35 2012 +0200
@@ -28,6 +28,7 @@
     def __init__(self):
         debug(_("Contact List init"))
         self._cache = {}
+        self.specials={}
 
     def update_jid(self, jid):
         """Update the jid in the list when something changed"""
@@ -51,31 +52,34 @@
     
     def clearContacts(self, jid):
         """Clear all the contact list"""
-        raise NotImplementedError
+        self.specials.clear()
     
-    def _replace(self, jid, groups=None, attributes=None):
+    def replace(self, jid, groups=None, attributes=None):
+        """add a contact to the list if doesn't exist, else update it"""
         if attributes and 'name' in attributes:
             self.setCache(jid, 'name', attributes['name'])
-        self.replace(jid, groups, attributes)
-    
-    def replace(self, jid, groups, attributes):
-        """add a contact to the list if doesn't exist, else update it"""
-        raise NotImplementedError
     
     def remove(self, jid):
         """remove a contact from the list"""
-        raise NotImplementedError
+        try:
+            del self.specials[jid.short]
+        except KeyError:
+            pass
 
     def add(self, jid, param_groups=None):
         """add a contact to the list"""
         raise NotImplementedError
 
+    def getSpecial(self, jid):
+        """Return special type of jid, or None if it's not special"""
+        return self.specials.get(jid.short)
+
     def setSpecial(self, jid, _type):
         """Set entity as a special
         @param jid: jid of the entity
         @param _type: special type (e.g.: "MUC")
         """
-        raise NotImplementedError
+        self.specials[jid.short] = _type
 
     def updatePresence(self, jid, show, priority, statuses):
         """Update entity's presence status
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/frontends/src/quick_frontend/quick_utils.py	Thu Oct 11 00:48:35 2012 +0200
@@ -0,0 +1,33 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Primitivus: a SAT frontend
+Copyright (C) 2009, 2010, 2011, 2012  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 Affero 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 Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from sat.tools.jid  import JID
+
+def escapePrivate(ori_jid):
+    """Escape a private jid"""
+    return JID(const_PRIVATE_PREFIX + ori_jid.short + '@' + ori_jid.resource)
+
+def unescapePrivate(escaped_jid):
+    if not escaped_jid.startswith(const_PRIVATE_PREFIX):
+        return escaped_jid
+    escaped_split = tuple(escaped_jid[len(const_PRIVATE_PREFIX):].split('@'))
+    assert(len(escaped_split) == 3)
+    return JID("%s@%s/%s" % escaped_split)
--- a/frontends/src/wix/chat.py	Fri Sep 28 00:48:52 2012 +0200
+++ b/frontends/src/wix/chat.py	Thu Oct 11 00:48:35 2012 +0200
@@ -201,7 +201,7 @@
 
     def onEnterPressed(self, event):
         """Behaviour when enter pressed in send line."""
-        self.host.bridge.sendMessage(self.target.short if self.type=='group' else self.target,
+        self.host.sendMessage(self.target.short if self.type=='group' else self.target,
                                      event.GetString(), 
                                      mess_type = "groupchat" if self.type=='group' else "chat",
                                      profile_key=self.host.profile)
--- a/frontends/src/wix/contact_list.py	Fri Sep 28 00:48:52 2012 +0200
+++ b/frontends/src/wix/contact_list.py	Thu Oct 11 00:48:35 2012 +0200
@@ -169,8 +169,7 @@
                     self.Insert(_present, gp_idx+1, contact)
 
     def setSpecial(self, special_jid, special_type):
-        pass
-
+        QuickContactList.setSpecial(self, special_jid, special_type)
 
     def remove(self, contact):
         """remove a contact from the list"""
--- a/frontends/src/wix/main_window.py	Fri Sep 28 00:48:52 2012 +0200
+++ b/frontends/src/wix/main_window.py	Thu Oct 11 00:48:35 2012 +0200
@@ -22,7 +22,6 @@
 
 from sat_frontends.quick_frontend.quick_chat_list import QuickChatList
 from sat_frontends.quick_frontend.quick_app import QuickApp
-from sat_frontends.quick_frontend.quick_contact_management import QuickContactManagement
 import wx
 from sat_frontends.wix.contact_list import ContactList
 from sat_frontends.wix.chat import Chat
@@ -31,9 +30,7 @@
 from sat_frontends.wix.gateways import GatewaysManager
 from sat_frontends.wix.profile import Profile
 from sat_frontends.wix.profile_manager import ProfileManager
-import gobject
 import os.path
-import pdb
 from sat.tools.jid  import JID
 from logging import debug, info, warning, error
 import sat_frontends.wix.constants
@@ -52,9 +49,6 @@
 class ChatList(QuickChatList):
     """This class manage the list of chat windows"""
     
-    def __init__(self, host):
-        QuickChatList.__init__(self, host)
-    
     def createChat(self, target):
         return Chat(target, self.host)
     
@@ -186,8 +180,8 @@
         wx.EVT_MENU(self, idFIND_GATEWAYS, self.onFindGateways)
 
 
-    def newMessage(self, from_jid, msg, type, to_jid, profile):
-        QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
+    def newMessage(self, from_jid, to_jid, msg, _type, profile):
+        QuickApp.newMessage(self, from_jid, to_jid, msg, _type, profile)
 
     def showAlert(self, message):
         # TODO: place this in a separate class