changeset 501:e9634d2e7b38

core, quick_frontend, primitivus, wix: Contacts List refactoring phase 1: - QuickContactManagement is not used anymore and will be removed, ContactList + Core are used instead - disconnected contacts are now displayed in Primitivus (M-d to show/hide them) - avatars are temporary unavailable in wix - new bridge method: getContactsFromGroup
author Goffi <goffi@goffi.org>
date Tue, 25 Sep 2012 00:58:34 +0200
parents 00d3679976ab
children debcf5dd404a
files frontends/src/bridge/DBus.py frontends/src/primitivus/chat.py frontends/src/primitivus/constants.py frontends/src/primitivus/contact_list.py frontends/src/primitivus/primitivus frontends/src/quick_frontend/quick_app.py frontends/src/quick_frontend/quick_chat.py frontends/src/quick_frontend/quick_contact_list.py frontends/src/wix/chat.py frontends/src/wix/contact_list.py frontends/src/wix/main_window.py src/bridge/DBus.py src/bridge/bridge_constructor/bridge_template.ini src/core/exceptions.py src/core/sat_main.py src/core/xmpp.py src/tools/misc.py
diffstat 17 files changed, 207 insertions(+), 130 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/bridge/DBus.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/bridge/DBus.py	Tue Sep 25 00:58:34 2012 +0200
@@ -96,6 +96,9 @@
     def getContacts(self, profile_key="@DEFAULT@"):
         return self.db_core_iface.getContacts(profile_key)
 
+    def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
+        return self.db_core_iface.getContactsFromGroup(group, profile_key)
+
     def getHistory(self, from_jid, to_jid, limit, between=True, callback=None, errback=None):
         return self.db_core_iface.getHistory(from_jid, to_jid, limit, between, reply_handler=callback, error_handler=lambda err:errback(err._dbus_error_name[len(const_ERROR_PREFIX)+1:]))
 
--- a/frontends/src/primitivus/chat.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/primitivus/chat.py	Tue Sep 25 00:58:34 2012 +0200
@@ -262,11 +262,8 @@
     def startGame(self, game_type, referee, players):
         """Configure the chat window to start a game"""
         if game_type=="Tarot":
-            try:
-                self.tarot_wid = CardGame(self, referee, players, self.nick)
-                self.__appendGamePanel(self.tarot_wid)
-            except e:
-                self.host.debug()
+            self.tarot_wid = CardGame(self, referee, players, self.nick)
+            self.__appendGamePanel(self.tarot_wid)
     
     def getGame(self, game_type):
         """Return class managing the game type"""
@@ -288,6 +285,11 @@
     #MISC EVENTS#
     def onFileSelected(self, filepath):
         self.host.removePopUp()
-        full_jid = self.host.CM.get_full(self.target)
+        #FIXME: check last_resource: what if self.target.resource exists ?
+        last_resource = self.host.bridge.getLastResource(unicode(self.target.short), self.host.profile)
+        if last_resource:
+            full_jid = JID("%s/%s" % (self.target.short, last_resource))
+        else:
+            full_jid = self.target
         id = self.host.bridge.sendFile(full_jid, filepath, {}, self.host.profile)
         self.host.addProgress(id,filepath) 
--- a/frontends/src/primitivus/constants.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/primitivus/constants.py	Tue Sep 25 00:58:34 2012 +0200
@@ -54,6 +54,7 @@
     ('warning', 'light red', 'default'),
     ('progress_normal', 'default', 'black'),
     ('progress_complete', 'default', 'light red'),
+    ('show_disconnected', 'dark gray', 'default'),
     ('show_normal', 'default', 'default'),
     ('show_normal_focus', 'default, bold', 'default'),
     ('show_chat', 'dark green', 'default'),
@@ -67,7 +68,8 @@
     ('status', 'yellow', 'default'),
     ('status_focus', 'yellow, bold', 'default'),
     ]
-__builtin__.__dict__['const_SHOW_ICON'] = {"": (u'✔', "show_normal"),
+__builtin__.__dict__['const_SHOW_ICON'] = {"unavailable": (u'⨯', "show_disconnected"), 
+                                          "": (u'✔', "show_normal"),
                                           "chat": (u'✆', "show_chat"),
                                           "away": (u'✈', "show_away"),
                                           "dnd": (u'✖', "show_dnd"),
--- a/frontends/src/primitivus/contact_list.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/primitivus/contact_list.py	Tue Sep 25 00:58:34 2012 +0200
@@ -28,12 +28,13 @@
 class ContactList(urwid.WidgetWrap, QuickContactList):
     signals = ['click','change']
 
-    def __init__(self, host, CM, on_click=None, on_change=None, user_data=None):
+    def __init__(self, host, on_click=None, on_change=None, user_data=None):
         self.host = host
         self.selected = None
         self.groups={}
         self.alert_jid=set()
         self.show_status = False
+        self.show_disconnected = False
         
         #we now build the widget
         self.frame = urwid.Frame(self.__buildList())
@@ -43,9 +44,9 @@
             urwid.connect_signal(self, 'click', on_click, user_data)
         if on_change:
             urwid.connect_signal(self, 'change', on_change, user_data)
-        QuickContactList.__init__(self, CM)
+        QuickContactList.__init__(self)
 
-    def _update(self):
+    def update(self):
         """Update display, keep focus"""
         widget, position = self.frame.body.get_focus()
         self.frame.body = self.__buildList()
@@ -53,10 +54,16 @@
             self.frame.body.set_focus(position)
         self.host.redraw()
 
+    def update_jid(self, jid):
+        self.update()
+
     def keypress(self, size, key):
         if key == "meta s": #user wants to (un)hide contacts' statuses
             self.show_status = not self.show_status
-            self._update()
+            self.update()
+        elif key == "meta d": #user wants to (un)hide disconnected contacts
+            self.show_disconnected = not self.show_disconnected
+            self.update()
         return super(ContactList, self).keypress(size, key) 
     
     def __contains__(self, jid):
@@ -77,12 +84,12 @@
     def putAlert(self, jid):
         """Put an alert on the jid to get attention from user (e.g. for new message)"""
         self.alert_jid.add(jid.short)
-        self._update()
+        self.update()
 
     def __groupClicked(self, group_wid):
         group = self.groups[group_wid.getValue()]
         group[0] = not group[0]
-        self._update()
+        self.update()
         self.setFocus(group_wid.getValue())
 
     def __contactClicked(self, contact_wid, selected):
@@ -92,7 +99,7 @@
                 widget.setState(widget.data == self.selected, invisible=True)
         if self.selected in self.alert_jid:
             self.alert_jid.remove(self.selected)
-            self._update()
+        self.update()
         self._emit('click')
 
     def __buildContact(self, content, param_contacts):
@@ -100,13 +107,20 @@
         @param content: widget list, e.g. SimpleListWalker
         @param contacts: list of JID"""
         contacts = list(param_contacts)
-        contacts.sort()
+        
+        widgets = [] #list of built widgets
+        
         for contact in contacts:
             jid=JID(contact) 
-            name = self.CM.getAttr(jid,'name')
-            nick = self.CM.getAttr(jid,'nick')
-            status = self.CM.getAttr(jid, 'status')
-            show = self.CM.getAttr(jid, 'show')
+            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]
@@ -118,6 +132,12 @@
                                                 selected = contact==self.selected,
                                                 header=header)
             widget.data = contact
+            widget.comp = contact_disp[1].lower() #value to use for sorting
+            widgets.append(widget)
+       
+        widgets.sort(key=lambda widget: widget.comp)
+
+        for widget in widgets:
             content.append(widget)
             urwid.connect_signal(widget, 'change', self.__contactClicked)
 
@@ -125,7 +145,7 @@
         """Build the main contact list widget"""
         content = urwid.SimpleListWalker([])
         group_keys = self.groups.keys()
-        group_keys.sort()
+        group_keys.sort(key = lambda x: x.lower() if x else x)
         for key in group_keys:
             unfolded = self.groups[key][0]
             if key!=None:
@@ -145,28 +165,30 @@
                 widget.setState(False, invisible=True)
 
 
-    def get_contact(self):
+    def getContact(self):
         """Return contact currently selected"""
         return self.selected
             
-    def clear_contacts(self):
+    def clearContacts(self):
         """clear all the contact list"""
         self.groups={}
         self.selected = None
         self.unselectAll()
-        self._update()
+        self.update()
 
-    def replace(self, jid, groups=[None]):
+    def replace(self, jid, groups=None, attributes=None):
         """add a contact to the list if doesn't exist, else update it"""
+        if not groups:
+            groups = [None]
+        if not attributes:
+            attributes={}
         assert isinstance(groups, list)
         assert isinstance(jid, JID)
-        if not groups:
-            groups=[None]
         for group in groups:
             if not self.groups.has_key(group):
                 self.groups[group] = [True,set()]  #[unfold,list_of_contacts]
             self.groups[group][1].add(jid.short)
-        self._update()
+        self.update()
 
 
         """contacts = self.list_wid.getAllValues()
@@ -176,10 +198,6 @@
             self.list_wid.changeValues(contacts)
             self._emit('change')"""
     
-    def disconnect(self, jid):
-        """mark a contact disconnected"""
-        self.remove(jid.short)
-    
     def remove(self, param_jid):
         """remove a contact from the list"""
         groups_to_remove = []
@@ -192,7 +210,7 @@
                     groups_to_remove.append(group)
         for group in groups_to_remove:
             del self.groups[group]
-        self._update()
+        self.update()
     
     def add(self, jid, param_groups=[None]):
         """add a contact to the list"""
--- a/frontends/src/primitivus/primitivus	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/primitivus/primitivus	Tue Sep 25 00:58:34 2012 +0200
@@ -56,7 +56,6 @@
 class PrimitivusApp(QuickApp):
     
     def __init__(self):
-        self.CM = QuickContactManagement() #FIXME: not the best place
         QuickApp.__init__(self)
         
         ## main loop setup ##
@@ -141,12 +140,12 @@
         elif input == 'f2': #user wants to (un)hide the contact_list
             try:
                 center_widgets = self.center_part.widget_list
-                if self.contactList in center_widgets:
+                if self.contact_list in center_widgets:
                     self.center_part.set_focus(0) #necessary as the focus change to the next object, we can go out of range if we are on the last object of self.center_part
-                    center_widgets.remove(self.contactList)
+                    center_widgets.remove(self.contact_list)
                     del self.center_part.column_types[0]
                 else:
-                    center_widgets.insert(0, self.contactList)
+                    center_widgets.insert(0, self.contact_list)
                     self.center_part.column_types.insert(0, ('weight', 2))
             except AttributeError:
                 #The main widget is not built (probably in Profile Manager)
@@ -196,9 +195,9 @@
         return menu_roller 
 
     def __buildMainWidget(self):
-        self.contactList = ContactList(self, self.CM, on_click = self.contactSelected, on_change=lambda w: self.redraw())
-        #self.center_part = urwid.Columns([('weight',2,self.contactList),('weight',8,Chat('',self))])
-        self.center_part = urwid.Columns([('weight',2,self.contactList), ('weight',8,urwid.Filler(urwid.Text('')))])
+        self.contact_list = ContactList(self, on_click = self.contactSelected, on_change=lambda w: self.redraw())
+        #self.center_part = urwid.Columns([('weight',2,self.contact_list),('weight',8,Chat('',self))])
+        self.center_part = urwid.Columns([('weight',2,self.contact_list), ('weight',8,urwid.Filler(urwid.Text('')))])
         self.editBar = sat_widgets.AdvancedEdit(u'> ')
         self.editBar.setCompletionMethod(self._nick_completion)
         urwid.connect_signal(self.editBar,'click',self.onTextEntered)
@@ -209,7 +208,7 @@
     def _nick_completion(self, text, completion_data):
         """Completion method which complete pseudo in group chat
         for params, see AdvancedEdit"""
-        contact = self.contactList.get_contact() ###Based on the fact that there is currently only one contact selectable at once
+        contact = self.contact_list.getContact() ###Based on the fact that there is currently only one contact selectable at once
         if contact:
             chat = self.chat_wins[contact]
             if chat.type != "group":
@@ -266,7 +265,7 @@
         wid_idx = len(self.center_part.widget_list)-1
         self.center_part.widget_list[wid_idx] = widget
         self.menu_roller.removeMenu(_('Chat menu'))
-        self.contactList.unselectAll()
+        self.contact_list.unselectAll()
         self.redraw()
 
     def removeWindow(self):
@@ -289,7 +288,7 @@
         self.notBar.setProgress(percentage)
 
     def contactSelected(self, contact_list):
-        contact = contact_list.get_contact()
+        contact = contact_list.getContact()
         if contact:
             assert(len(self.center_part.widget_list)==2)
             self.center_part.widget_list[1] = self.chat_wins[contact]
@@ -297,7 +296,7 @@
 
     def onTextEntered(self, editBar):
         """Called when text is entered in the main edit bar"""
-        contact = self.contactList.get_contact() ###Based on the fact that there is currently only one contact selectableat once
+        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,
@@ -311,8 +310,8 @@
             return
         QuickApp.newMessage(self, from_jid, msg, type, to_jid, profile)
         sender = JID(from_jid)
-        if JID(self.contactList.selected).short != sender.short:
-            self.contactList.putAlert(sender)
+        if JID(self.contact_list.selected).short != sender.short:
+            self.contact_list.putAlert(sender)
 
     def _dialogOkCb(self, widget, data):
         self.removePopUp()
@@ -422,8 +421,8 @@
 
     def onRemoveContact(self, button):
         self.removePopUp()
-        info(_("Unsubscribing %s presence"),self.contactList.get_contact())
-        self.bridge.delContact(self.contactList.get_contact(), profile_key=self.profile)
+        info(_("Unsubscribing %s presence"),self.contact_list.getContact())
+        self.bridge.delContact(self.contact_list.getContact(), profile_key=self.profile)
 
     #MENU EVENTS#
     def onConnectRequest(self, menu):
@@ -460,7 +459,7 @@
         self.showPopUp(pop_up_widget)
 
     def onRemoveContactRequest(self, menu):
-        contact = self.contactList.get_contact()
+        contact = self.contact_list.getContact()
         if not contact:
             self.showPopUp(sat_widgets.Alert(_("Error"), _("You have not selected any contact to delete !"), ok_cb=self.removePopUp))
         else:
--- a/frontends/src/quick_frontend/quick_app.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/quick_frontend/quick_app.py	Tue Sep 25 00:58:34 2012 +0200
@@ -19,7 +19,7 @@
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-from logging import debug, info, error
+from logging import debug, info, warning, error
 from sat.tools.jid  import JID
 from sat_frontends.bridge.DBus import DBusBridgeFrontend,BridgeExceptionNoService
 from optparse import OptionParser
@@ -32,7 +32,6 @@
     """This class contain the main methods needed for the frontend"""
 
     def __init__(self, single_profile=True):
-        self.rosterList = {}
         self.profiles = {}
         self.single_profile = single_profile
         self.check_options()
@@ -157,7 +156,7 @@
 
             ### now we fill the contact list ###
             for contact in self.bridge.getContacts(profile):
-                self.newContact(contact[0], contact[1], contact[2], profile)
+                self.newContact(*contact, profile=profile)
 
             presences = self.bridge.getPresenceStatus(profile)
             for contact in presences:
@@ -207,8 +206,7 @@
         if not self.check_profile(profile):
             return
         debug(_("Disconnected"))
-        self.CM.clear()
-        self.contactList.clear_contacts()
+        self.contactList.clearContacts()
         self.setStatusOnline(False)
     
     def connectionError(self, error_type, profile):
@@ -227,10 +225,7 @@
             return
         entity=JID(JabberId)
         _groups = list(groups)
-        self.rosterList[entity.short]=(dict(attributes), _groups)
-        if entity in self.CM:
-            self.CM.update(entity, 'groups', _groups)
-            self.contactList.replace(entity, self.CM.getAttr(entity, 'groups'))
+        self.contact_list._replace(entity, _groups, attributes)
     
     def newMessage(self, from_jid, msg, type, to_jid, profile):
         if not self.check_profile(profile):
@@ -266,36 +261,20 @@
                 self.setStatusOnline(False)
             return
 
+        self.contact_list.updatePresence(from_jid, show, priority, statuses) 
+            
         if show != 'unavailable':
-            name=""
-            groups = []
-            if self.rosterList.has_key(from_jid.short):
-                if self.rosterList[from_jid.short][0].has_key("name"):
-                    name=self.rosterList[from_jid.short][0]["name"]
-                groups=self.rosterList[from_jid.short][1]
-
+            
             #FIXME: must be moved in a plugin
             if from_jid.short in self.profiles[profile]['watched'] and not from_jid.short in self.profiles[profile]['onlineContact']:
                 self.showAlert(_("Watched jid [%s] is connected !") % from_jid.short)
 
             self.profiles[profile]['onlineContact'].add(from_jid)  #FIXME onlineContact is useless with CM, must be removed
-            self.CM.add(from_jid)
-            self.CM.update(from_jid, 'name', unicode(name))
-            self.CM.update(from_jid, 'show', show)
-            self.CM.update(from_jid, 'statuses', statuses)
-            self.CM.update(from_jid, 'groups', groups)
-            cache = self.bridge.getCardCache(from_jid, profile)
-            if cache.has_key('nick'): 
-                self.CM.update(from_jid, 'nick', unicode(cache['nick']))
-            if cache.has_key('avatar'): 
-                self.CM.update(from_jid, 'avatar', self.bridge.getAvatarFile(cache['avatar']))
-            self.contactList.replace(from_jid, self.CM.getAttr(from_jid, 'groups'))
+           
+            #TODO: vcard data (avatar)
 
         if show=="unavailable" and from_jid in self.profiles[profile]['onlineContact']:
             self.profiles[profile]['onlineContact'].remove(from_jid)
-            self.CM.remove(from_jid)
-            if not self.CM.isConnected(from_jid):
-                self.contactList.disconnect(from_jid)
     
     def roomJoined(self, room_jid, room_nicks, user_nick, profile):
         """Called when a MUC room is joined"""
@@ -491,27 +470,27 @@
         if not self.check_profile(profile):
             return
         target = JID(jid)
-        self.contactList.remove(self.CM.get_full(target))
-        self.CM.remove(target)
+        self.contactList.remove(target)
         try:
             self.profiles[profile]['onlineContact'].remove(target.short)
         except KeyError:
             pass
 
     def updatedValue(self, name, data, profile):
+        #FIXME: to be removed
         if not self.check_profile(profile):
             return
         if name == "card_nick":
             target = JID(data['jid'])
             if target in self.contactList:
-                self.CM.update(target, 'nick', unicode(data['nick']))
-                self.contactList.replace(target)
+                #self.CM.update(target, 'nick', unicode(data['nick']))
+                self.contact_list._replace(target)
         elif name == "card_avatar":
             target = JID(data['jid'])
             if target in self.contactList:
                 filename = self.bridge.getAvatarFile(data['avatar'])
-                self.CM.update(target, 'avatar', filename)
-                self.contactList.replace(target)
+                #self.CM.update(target, 'avatar', filename)
+                self.contact_list._replace(target)
 
     def askConfirmation(self, type, id, data):
         raise NotImplementedError
--- a/frontends/src/quick_frontend/quick_chat.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/quick_frontend/quick_chat.py	Tue Sep 25 00:58:34 2012 +0200
@@ -99,7 +99,7 @@
 
     def _get_nick(self, jid):
         """Return nick of this jid when possible"""
-        return jid.resource if self.type == "group" else (self.host.CM.getAttr(jid,'nick') or self.host.CM.getAttr(jid,'name') or jid.node)
+        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):
         """Print message in chat window. Must be implemented by child class"""
--- a/frontends/src/quick_frontend/quick_contact_list.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/quick_frontend/quick_contact_list.py	Tue Sep 25 00:58:34 2012 +0200
@@ -19,35 +19,49 @@
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
-from logging import debug, info, error
-from sat.tools.jid  import JID
+from logging import debug
 
 
 class QuickContactList():
     """This class manage the visual representation of contacts"""
 
-    def __init__(self, CM):
-        """
-        @param CM: instance of QuickContactManagement
-        """
+    def __init__(self):
         debug(_("Contact List init"))
-        self.CM = CM
+        self._cache = {}
+
+    def update_jid(self, jid):
+        """Update the jid in the list when something changed"""
+        raise NotImplementedError
+
+    def getCache(self, jid, name):
+        try:
+            jid_cache = self._cache[jid.short]
+            if name == 'status': #XXX: we get the first status for 'status' key
+                return jid_cache['statuses'].get('default','')
+            return jid_cache[name]
+        except (KeyError, IndexError):
+            return None
+
+    def setCache(self, jid, name, value):
+        jid_cache = self._cache.setdefault(jid.short, {})
+        jid_cache[name] = value
 
     def __contains__(self, jid):
         raise NotImplementedError
     
-    def clear_contacts(self, jid):
+    def clearContacts(self, jid):
         """Clear all the contact list"""
         raise NotImplementedError
     
-    def replace(self, jid, groups=None):
+    def _replace(self, jid, groups=None, attributes=None):
+        if '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 disconnect(self, jid):
-        """mark a contact disconnected"""
-        raise NotImplementedError
-    
     def remove(self, jid):
         """remove a contact from the list"""
         raise NotImplementedError
@@ -55,3 +69,14 @@
     def add(self, jid, param_groups=None):
         """add a contact to the list"""
         raise NotImplementedError
+
+    def updatePresence(self, jid, show, priority, statuses):
+        """Update entity's presence status
+        @param jid: entity to update's jid
+        @param show: availability
+        @parap priority: resource's priority
+        @param statuses: dict of statuses"""
+        self.setCache(jid, 'show', show)
+        self.setCache(jid, 'prority', priority)
+        self.setCache(jid, 'statuses', statuses)
+        self.update_jid(jid)
--- a/frontends/src/wix/chat.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/wix/chat.py	Tue Sep 25 00:58:34 2012 +0200
@@ -258,7 +258,12 @@
         filename = wx.FileSelector(_("Choose a file to send"), flags = wx.FD_FILE_MUST_EXIST)
         if filename:
             debug(_("filename: %s"),filename)
-            full_jid = self.host.CM.get_full(self.target)
+            #FIXME: check last_resource: what if self.target.resource exists ?
+            last_resource = self.host.bridge.getLastResource(unicode(self.target.short), self.host.profile)
+            if last_resource:
+                full_jid = JID("%s/%s" % (self.target.short, last_resource))
+            else:
+                full_jid = self.target
             id = self.host.bridge.sendFile(full_jid, filename, {}, self.host.profile)
             self.host.waitProgress(id, _("File Transfer"), _("Copying %s") % os.path.basename(filename)) 
 
--- a/frontends/src/wix/contact_list.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/wix/contact_list.py	Tue Sep 25 00:58:34 2012 +0200
@@ -44,7 +44,7 @@
                                            "CUSTOM" for a customized contact list (self.__presentItem must then be overrided)
         """
         wx.SimpleHtmlListBox.__init__(self, parent, -1)
-        QuickContactList.__init__(self, host.CM)
+        QuickContactList.__init__(self)
         self.host = host
         self.type = type
         self.__typeSwitch()
@@ -72,17 +72,19 @@
                 result.append(i)
         return result
 
-    def replace(self, contact, groups=None):
+    def update_jid(self, jid):
+        self.replace(jid)
+
+    def replace(self, contact, groups=None, attributes=None):
         debug(_("update %s") % contact)
         if not self.__find_idx(contact):
             self.add(contact, groups)
         else:
             for i in self.__find_idx(contact):
-                self.SetString(i, self.__presentItem(contact))
+                _present = self.__presentItem(contact)
+                if _present != None:
+                    self.SetString(i, _present)
 
-    def disconnect(self, contact):
-        self.remove(contact) #for now, we only show online contacts
-    
     def __eraseGroup(self, group):
         """Erase all contacts in group
         @param group: group to erase
@@ -108,15 +110,19 @@
     
     def __presentItemJID(self, jid):
         """Make a nice presentation of the contact in the list for JID contacts."""
-        name = self.CM.getAttr(jid,'name')
-        nick = self.CM.getAttr(jid,'nick')
-        show =  filter(lambda x:x[0]==self.CM.getAttr(jid,'show'), const_STATUS)[0]
+        name = self.getCache(jid,'name')
+        nick = self.getCache(jid,'nick')
+        _show = self.getCache(jid,'show')
+        if _show == None or _show == 'unavailable':
+            return None
+        show =  filter(lambda x : x[0] == _show, const_STATUS)[0]
+            
         #show[0]==shortcut
         #show[1]==human readable
         #show[2]==color (or None)
         show_html = "<font color='%s'>[%s]</font>" % (show[2], show[1]) if show[2] else ""
-        status = self.CM.getAttr(jid,'status') or ''
-        avatar = self.CM.getAttr(jid,'avatar') or self.empty_avatar #XXX: there is a weird bug here: if the image has an extension (i.e. empty_avatar.png),
+        status = self.getCache(jid,'status') or ''
+        avatar = self.getCache(jid,'avatar') or self.empty_avatar #XXX: there is a weird bug here: if the image has an extension (i.e. empty_avatar.png),
         #WxPython segfault, and it doesn't without nothing. I couldn't reproduce the case with a basic test script, so it need further investigation before reporting it
         #to WxPython dev. Anyway, the program crash with a segfault, not a python exception, so there is definitely something wrong with WxPython.
         #The case seems to happen when SimpleHtmlListBox parse the HTML with the <img> tag
@@ -138,7 +144,7 @@
 
         return html
 
-    def clear_contacts(self):
+    def clearContacts(self):
         """Clear all the contact list"""
         self.Clear()
 
@@ -146,7 +152,9 @@
         """add a contact to the list"""
         debug (_("adding %s"),contact)
         if not groups:
-            idx = self.Insert(self.__presentItem(contact), 0, contact)
+            _present =  self.__presentItem(contact)
+            if _present:
+                idx = self.Insert(_present, 0, contact)
         else:
             for group in groups:
                 indexes = self.__find_idx(group)
@@ -156,7 +164,9 @@
                 else:
                     gp_idx = indexes[0]
                 
-                self.Insert(self.__presentItem(contact), gp_idx+1, contact)
+                _present = self.__presentItem(contact)
+                if _present:
+                    self.Insert(_present, gp_idx+1, contact)
 
 
 
@@ -176,11 +186,13 @@
             group = self.GetClientData(self.GetSelection())
             erased = self.__eraseGroup(group)
             if not erased: #the group was already erased, we can add again the contacts
-                contacts = self.CM.getContFromGroup(group)
+                contacts = [JID(contact) for contact in self.host.bridge.getContactsFromGroup(group, self.host.profile)]
                 contacts.sort()
                 id_insert = self.GetSelection()+1
                 for contact in contacts:
-                    self.Insert(self.__presentItem(contact), id_insert, contact)
+                    _present =  self.__presentItem(contact)
+                    if _present:
+                        self.Insert(_present, id_insert, contact)
             self.SetSelection(wx.NOT_FOUND)
             self.ScrollToLine(first_visible)
             event.Skip(False)
--- a/frontends/src/wix/main_window.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/frontends/src/wix/main_window.py	Tue Sep 25 00:58:34 2012 +0200
@@ -64,17 +64,16 @@
     def __init__(self):
         QuickApp.__init__(self)
         wx.Frame.__init__(self,None, title="SàT Wix", size=(300,500))
-        self.CM = QuickContactManagement() #FIXME: not the best place
 
         #sizer
         self.sizer = wx.BoxSizer(wx.VERTICAL)
         self.SetSizer(self.sizer)
         
         #Frame elements
-        self.contactList = ContactList(self, self)
-        self.contactList.registerActivatedCB(self.onContactActivated)
-        self.contactList.Hide()
-        self.sizer.Add(self.contactList, 1, flag=wx.EXPAND)
+        self.contact_list = ContactList(self, self)
+        self.contact_list.registerActivatedCB(self.onContactActivated)
+        self.contact_list.Hide()
+        self.sizer.Add(self.contact_list, 1, flag=wx.EXPAND)
         
         self.chat_wins=ChatList(self)
         self.CreateStatusBar()
@@ -120,7 +119,7 @@
         """Hide profile panel then plug profile"""
         debug (_('plugin profile %s' % profile_key))
         self.profile_pan.Hide()
-        self.contactList.Show()
+        self.contact_list.Show()
         self.sizer.Layout()
         for i in range(self.menuBar.GetMenuCount()):
             self.menuBar.EnableTop(i, True)
@@ -410,7 +409,7 @@
 
     def onRemoveContact(self, e):
         debug(_("Remove contact request"))
-        target = self.contactList.getSelection()
+        target = self.contact_list.getSelection()
         if not target:
             dlg = wx.MessageDialog(self, _("You haven't selected any contact !"),
                                    _('Error'),
@@ -433,7 +432,7 @@
 
     def onShowProfile(self, e):
         debug(_("Show contact's profile request"))
-        target = self.contactList.getSelection()
+        target = self.contact_list.getSelection()
         if not target:
             dlg = wx.MessageDialog(self, _("You haven't selected any contact !"),
                                    _('Error'),
--- a/src/bridge/DBus.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/src/bridge/DBus.py	Tue Sep 25 00:58:34 2012 +0200
@@ -256,6 +256,12 @@
         return self._callback("getContacts", unicode(profile_key))
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
+                         in_signature='ss', out_signature='as',
+                         async_callbacks=None)
+    def getContactsFromGroup(self, group, profile_key="@DEFAULT@"):
+        return self._callback("getContactsFromGroup", unicode(group), unicode(profile_key))
+
+    @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='ssib', out_signature='a(dsss)',
                          async_callbacks=('callback', 'errback'))
     def getHistory(self, from_jid, to_jid, limit, between=True, callback=None, errback=None):
@@ -366,7 +372,7 @@
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
                          in_signature='sssss', out_signature='',
                          async_callbacks=None)
-    def sendMessage(self, to_jid, message, subject='', mess_type="chat", profile_key="@DEFAULT@"):
+    def sendMessage(self, to_jid, message, subject='', mess_type="auto", profile_key="@DEFAULT@"):
         return self._callback("sendMessage", unicode(to_jid), unicode(message), unicode(subject), unicode(mess_type), unicode(profile_key))
 
     @dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
--- a/src/bridge/bridge_constructor/bridge_template.ini	Wed Sep 05 00:19:32 2012 +0200
+++ b/src/bridge/bridge_constructor/bridge_template.ini	Tue Sep 25 00:58:34 2012 +0200
@@ -271,6 +271,17 @@
  - list of attributes as in [newContact]
  - groups where the contact is
 
+[getContactsFromGroup]
+type=method
+category=core
+sig_in=ss
+sig_out=as
+param_1_default="@DEFAULT@"
+doc=Return informations about all contacts
+doc_param_0=group: name of the group to check
+doc_param_1=%(doc_profile_key)s
+doc_return=array of jids
+
 [getLastResource]
 type=method
 category=core
--- a/src/core/exceptions.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/src/core/exceptions.py	Tue Sep 25 00:58:34 2012 +0200
@@ -30,3 +30,6 @@
 
 class UnknownEntityError(Exception):
     pass
+
+class UnknownGroupError(Exception):
+    pass
--- a/src/core/sat_main.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/src/core/sat_main.py	Tue Sep 25 00:58:34 2012 +0200
@@ -126,6 +126,7 @@
         self.bridge.register("asyncConnect", self.asyncConnect)
         self.bridge.register("disconnect", self.disconnect)
         self.bridge.register("getContacts", self.getContacts)
+        self.bridge.register("getContactsFromGroup", self.getContactsFromGroup)
         self.bridge.register("getLastResource", self.memory.getLastResource)
         self.bridge.register("getPresenceStatus", self.memory.getPresenceStatus)
         self.bridge.register("getWaitingSub", self.memory.getWaitingSub)
@@ -276,8 +277,7 @@
     def getContacts(self, profile_key):
         client = self.getClient(profile_key)
         if not client:
-            error(_('Asking contacts for a non-existant profile'))
-            return []
+            raise ProfileUnknownError(_('Asking contacts for a non-existant profile'))
         ret = []
         for item in client.roster.getItems(): #we get all item for client's roster
             #and convert them to expected format
@@ -285,6 +285,12 @@
             ret.append([item.jid.userhost(), attr, item.groups])
         return ret
 
+    def getContactsFromGroup(self, group, profile_key):
+        client = self.getClient(profile_key)
+        if not client:
+            raise ProfileUnknownError(_("Asking group's contacts for a non-existant profile"))
+        return client.roster.getJidsFromGroup(group)
+
     def purgeClient(self, profile):
         """Remove reference to a profile client and purge cache
         the garbage collector can then free the memory"""
--- a/src/core/xmpp.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/src/core/xmpp.py	Tue Sep 25 00:58:34 2012 +0200
@@ -23,6 +23,7 @@
 from twisted.words.protocols.jabber import jid, xmlstream
 from wokkel import client, disco, xmppim, generic, compat
 from logging import debug, info, error
+from sat.core import exceptions
 
 
 class SatXMPPClient(client.XMPPClient):
@@ -164,9 +165,9 @@
         """Return dictionary of attributes as used in bridge from a RosterItem
         @param item: RosterItem
         @return: dictionary of attributes"""
-        item_attr = {'to': str(item.subscriptionTo),
-                     'from': str(item.subscriptionFrom),
-                     'ask': str(item.ask)
+        item_attr = {'to': unicode(item.subscriptionTo),
+                     'from': unicode(item.subscriptionFrom),
+                     'ask': unicode(item.ask)
                      }
         if item.name:
             item_attr['name'] = item.name
@@ -235,6 +236,12 @@
     def getItems(self):
         """Return all items of the roster"""
         return self._jids.values()
+
+    def getJidsFromGroup(self, group):
+        try:
+            return self._groups[group]
+        except KeyError:
+            return  exceptions.UnknownGroupError
         
 
 class SatPresenceProtocol(xmppim.PresenceClientProtocol):
--- a/src/tools/misc.py	Wed Sep 05 00:19:32 2012 +0200
+++ b/src/tools/misc.py	Tue Sep 25 00:58:34 2012 +0200
@@ -55,7 +55,7 @@
         """This put a trigger point
         All the trigger for that point will be run
         @param point_name: name of the trigger point
-        @return: True if the action must be continue, False else"""
+        @return: True if the action must be continued, False else"""
         if not self.__triggers.has_key(point_name):
             return True