bigboard r7241 - in trunk/bigboard: . stocks/people



Author: otaylor
Date: Tue Jan 29 22:55:57 2008
New Revision: 7241
URL: http://svn.gnome.org/viewvc/bigboard?rev=7241&view=rev

Log:
people_tracker.py: Substantially rewrite back to something more like the
  original vision where we canonicalize each User/Contact/Buddy to a 
  resource that "best represents" that person, then merge information
  from all the resources we know about starting from that resource
  together.

PeopleStock peoplebrowser peoplewidgets: Adapt to PeopleTracker changes


Modified:
   trunk/bigboard/people_tracker.py
   trunk/bigboard/stocks/people/PeopleStock.py
   trunk/bigboard/stocks/people/peoplebrowser.py
   trunk/bigboard/stocks/people/peoplewidgets.py

Modified: trunk/bigboard/people_tracker.py
==============================================================================
--- trunk/bigboard/people_tracker.py	(original)
+++ trunk/bigboard/people_tracker.py	Tue Jan 29 22:55:57 2008
@@ -29,302 +29,403 @@
     __gsignals__ = {
         "display-name-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
         "icon-url-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
-        "aim-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
-        "aim-buddy-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
-        "xmpp-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
-        "xmpp-buddy-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
-        "local-buddy-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "aims-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "aim-buddies-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "emails-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "xmpps-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "xmpp-buddies-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "local-buddies-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
         "online-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+        "status-changed": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
     }
     
     def __init__(self, resource):
         gobject.GObject.__init__(self)
-        self.resource = resource
-        self.is_contact = self.resource.class_id == "http://online.gnome.org/p/o/contact";
 
-        #self._debug_rank = -100
+        self.display_name = None
+        self.contact = None
+        self.user = None
+        self.buddy = None
+        self.aims = []
+        self.emails = []
+        self.xmpps = []
+        self.aim_buddies = []
+        self.xmpp_buddies = []
+        self.local_buddies = []
 
         self.icon_url = None
         self.online = False
-        self.aim_buddy = None
-        self.xmpp_buddy = None
-
-        if self.is_contact:
-            self.resource.connect(self.__contact_name_changed, "name")
-            self.resource.connect(self.__contact_aims_changed, "aims")
-            self.resource.connect(self.__contact_xmpps_changed, "xmpps")
-            self.resource.connect(self.__contact_aim_buddies_changed, "aimBuddies")
-            self.resource.connect(self.__contact_xmpp_buddies_changed, "xmppBuddies")
-
-            self.resource.connect(self.__contact_user_changed, "user")
-
-            self.local_buddy = None
-
-            self.__contact_user_changed(resource)
-            self.__contact_name_changed(resource)
-            self.__contact_aims_changed(resource)
-            self.__contact_xmpps_changed(resource)
-            self.__contact_aim_buddies_changed(resource)
-            self.__contact_xmpp_buddies_changed(resource)
-
-        else:
-            if resource.class_id != 'online-desktop:/p/o/buddy':
-                raise Exception("unknown class ID %s for resource constructing Person" % resource.class_id)
-
-            if resource.protocol == 'aim':
-                self.aim = resource.name
-                self.aim_buddy = resource
-                self.aims = [self.aim]
-                self.aim_buddies = [self.aim_buddy]
-            else:
-                self.aim = None
-                self.aim_buddy = None
-                self.aims = []
-                self.aim_buddies = []
-
-            if resource.protocol == 'xmpp':
-                self.xmpp = resource.name
-                self.xmpp_buddy = resource
-                self.xmpps = [self.xmpp]
-                self.xmpp_buddies = [self.xmpp_buddy]
-            else:
-                self.xmpp = None
-                self.xmpp_buddy = None
-                self.xmpps = []
-                self.xmpp_buddies = []
-
-            if resource.protocol == 'mugshot-local':
-                self.local_buddy = resource
-
-                ## The merge code in the data model won't know about the user 
-                ## corresponding to the local buddy, so the merge rule won't 
-                ## trigger to create local_buddy.user, unless we do this
-                _logger.debug("Ensuring we load user %s" % (self.local_buddy.name))
-                query = bigboard.globals.get_data_model().query_resource(self.local_buddy.name, "+")
-                query.execute()
-            else:
-                self.local_buddy = None
-
-            self.resource.connect(self.__buddy_alias_changed, "alias")
-            self.resource.connect(self.__buddy_icon_changed, "icon")
-            self.resource.connect(self.__buddy_online_changed, "isOnline")
-
-            self.__buddy_alias_changed(resource)
-            self.__buddy_icon_changed(resource)
-            self.__buddy_online_changed(resource)
-
-    def __contact_recompute_online(self):
-        isOnline = False
-        
-        try:
-            isOnline = self.aim_buddy.isOnline
-        except AttributeError:
-            pass
+        self.status = STATUS_NOT_A_CONTACT
 
-        if not isOnline:
-            try:
-                isOnline = self.xmpp_buddy.isOnline
-            except AttributeError:
-                pass
+        self.resource = resource
         
-        if isOnline != self.online:
-            self.online = isOnline
-            self.emit('online-changed')
-            _logger.debug('%s is now %d' % (self.display_name, self.online))
-
-    def __contact_name_changed(self, resource):
-        try:
-            self.display_name = resource.name
-        except AttributeError:
-            # FIXME: why does this happen
-            self.display_name = "NO_NAME_"
+        if resource.class_id == "http://online.gnome.org/p/o/contact":
+            self.__set_contact(resource)
+        elif resource.class_id == "http://mugshot.org/p/o/user":
+            self.__set_user(resource)
+        elif resource.class_id == "online-desktop:/p/o/buddy":
+            self.__set_buddy(resource)
+
+    def __set_contact(self, contact):
+        if self.contact:
+            self.contact.disconnect(self.__contact_user_changed)
+            self.contact.disconnect(self.__contact_name_changed)
+            self.contact.disconnect(self.__contact_status_changed)
+            self.contact.disconnect(self.__contact_aims_changed)
+            self.contact.disconnect(self.__contact_emails_changed)
+            self.contact.disconnect(self.__contact_xmpps_changed)
+            self.contact.disconnect(self.__contact_aim_buddies_changed)
+            self.contact.disconnect(self.__contact_xmpp_buddies_changed)
+
+        self.contact = contact
+        
+        if self.contact:
+            self.contact.connect(self.__contact_user_changed, "user")
+            self.contact.connect(self.__contact_name_changed, "name")
+            self.contact.connect(self.__contact_status_changed, "status")
+            self.contact.connect(self.__contact_aims_changed, "aims")
+            self.contact.connect(self.__contact_emails_changed, "aims")
+            self.contact.connect(self.__contact_xmpps_changed, "xmpps")
+            self.contact.connect(self.__contact_aim_buddies_changed, "aimBuddies")
+            self.contact.connect(self.__contact_xmpp_buddies_changed, "xmppBuddies")
             
-        self.emit("display-name-changed")
-
-    def __contact_aims_changed(self, resource):
-        try:
-            self.aims = resource.aims
-        except AttributeError:
-            self.aims = []
-
-        ## FIXME don't just pick one arbitrarily
-        if len(self.aims) > 0:
-            self.aim = self.aims[0]
-        else:
-            self.aim = None
-
-        self.emit("aim-changed")
-        self.__contact_recompute_online()
-
-    def __contact_xmpps_changed(self, resource):
-        try:
-            self.xmpps = resource.xmpps
-        except AttributeError:
-            self.xmpps = []
+        self.__contact_user_changed(contact)
+        self.__contact_name_changed(contact)
+        self.__contact_status_changed(contact)
+        self.__contact_aims_changed(contact)
+        self.__contact_emails_changed(contact)
+        self.__contact_xmpps_changed(contact)
+        self.__contact_aim_buddies_changed(contact)
+        self.__contact_xmpp_buddies_changed(contact)
+
+    def __set_user(self, user):
+        if self.user:
+            self.user.disconnect(self.__user_name_changed)
+            self.user.disconnect(self.__user_photo_url_changed)
+            self.user.disconnect(self.__user_aims_changed)
+            self.user.disconnect(self.__user_emails_changed)
+            self.user.disconnect(self.__user_xmpps_changed)
+            self.user.disconnect(self.__user_aim_buddies_changed)
+            self.user.disconnect(self.__user_xmpp_buddies_changed)
+            self.user.disconnect(self.__user_local_buddies_changed)
+
+        self.user = user
+        
+        if self.user:
+            self.user.connect(self.__user_name_changed, "name")
+            self.user.connect(self.__user_photo_url_changed, "photoUrl")
+            self.user.connect(self.__user_aims_changed, "aims")
+            self.user.connect(self.__user_emails_changed, "emails")
+            self.user.connect(self.__user_xmpps_changed, "xmpps")
+            self.user.connect(self.__user_aim_buddies_changed, "aimBuddies")
+            self.user.connect(self.__user_xmpp_buddies_changed, "xmppBuddies")
+            self.user.connect(self.__user_local_buddies_changed, "mugshotLocalBuddies")
+            
+        self.__user_name_changed(user)
+        self.__user_aims_changed(user)
+        self.__user_emails_changed(user)
+        self.__user_xmpps_changed(user)
+        self.__user_aim_buddies_changed(user)
+        self.__user_xmpp_buddies_changed(user)
+        self.__user_photo_url_changed(user)
+        self.__user_local_buddies_changed(user)
+
+    def __set_buddy(self, buddy):
+        self.buddy = buddy
+        
+        self.__update_aim_buddies()
+        self.__update_xmpp_buddies()
+        self.__update_local_buddies()
         
-        ## FIXME don't just pick one arbitrarily
-        if len(self.xmpps) > 0:
-            self.xmpp = self.xmpps[0]
-        else:
-            self.xmpp = None
-
-        self.emit("xmpp-changed")
-        self.__contact_recompute_online()    
-
-    def __contact_buddy_online_changed(self, *args):
-        self.__contact_recompute_online()
-
-    def __contact_aim_buddies_changed(self, resource):
-        try:
-            self.aim_buddies = resource.aimBuddies
-        except AttributeError:
-            self.aim_buddies = []
-
-        old_aim_buddy = self.aim_buddy
-
-        ## FIXME don't just pick one arbitrarily
-        if len(self.aim_buddies) > 0:
-            self.aim_buddy = self.aim_buddies[0]
-        else:
-            self.aim_buddy = None
-
-        self.__refresh_icon_url() # in case we were using the AIM buddy icon
-
-        if old_aim_buddy != self.aim_buddy:
-            if old_aim_buddy:
-                old_aim_buddy.disconnect(self.__contact_buddy_online_changed)
-            if self.aim_buddy:
-                self.aim_buddy.connect(self.__contact_buddy_online_changed, 'isOnline')
-            self.emit("aim-buddy-changed")
-            self.__contact_recompute_online()
-
-    def __contact_xmpp_buddies_changed(self, resource):
-        try:
-            self.xmpp_buddies = resource.xmppBuddies
-        except AttributeError:
-            self.xmpp_buddies = []
-
-        old_xmpp_buddy = self.xmpp_buddy
-
-        ## FIXME don't just pick one arbitrarily
-        if len(self.xmpp_buddies) > 0:
-            self.xmpp_buddy = self.xmpp_buddies[0]
-        else:
-            self.xmpp_buddy = None
-
-        self.__refresh_icon_url() # in case we were using the XMPP buddy icon
-
-        if old_xmpp_buddy != self.xmpp_buddy:
-            if old_xmpp_buddy:
-                old_xmpp_buddy.disconnect(self.__contact_buddy_online_changed)
-            if self.xmpp_buddy:
-                self.xmpp_buddy.connect(self.__contact_buddy_online_changed, 'isOnline')
-            self.emit("xmpp-buddy-changed")
-            self.__contact_recompute_online()
-
-    def __contact_user_changed(self, resource):
-        try:
-            user_resource = resource.user
-        except AttributeError:
-            user_resource = None
-
-        _logger.debug("user changed to %s" % str(user_resource))
-
-        if user_resource:
-            user_resource.connect(self.__user_photo_url_changed, "photoUrl")
-            user_resource.connect(self.__user_local_buddy_changed, "mugshotLocalBuddy")            
-
-        self.__user_local_buddy_changed(user_resource)
-        self.__user_photo_url_changed(user_resource)
+    ##############################################################
+        
+    def __contact_user_changed(self, contact):
+        self.__set_user(getattr(contact, 'user', None))
+    
+    def __contact_name_changed(self, contact):
+        self.__update_name()
+    
+    def __contact_status_changed(self, contact):
+        self.__update_status()
+    
+    def __contact_aims_changed(self, contact):
+        self.__update_aims()
+    
+    def __contact_emails_changed(self, contact):
+        self.__update_emails()
+    
+    def __contact_xmpps_changed(self, contact):
+        self.__update_xmpps()
+    
+    def __contact_aim_buddies_changed(self, contact):
+        self.__update_aim_buddies()
+    
+    def __contact_xmpp_buddies_changed(self, contact):
+        self.__update_xmpp_buddies()
 
-    def __user_local_buddy_changed(self, user_resource):
-        new_buddy = None
-        if user_resource:
-            try:
-                new_buddy = user_resource.mugshotLocalBuddy
-            except:
-                pass
-        
-        if new_buddy != self.local_buddy:
-            self.local_buddy = new_buddy
-            self.emit("local-buddy-changed")
-            self.__contact_recompute_online()
+    ##############################################################
 
-    def __set_icon_url(self, new_icon_url):
-        if not new_icon_url:
-            try:
-                new_icon_url = self.resource.model.global_resource.fallbackUserPhotoUrl
-            except AttributeError:
-                pass
+    def __user_name_changed(self, user):
+        self.__update_name()
+    
+    def __user_photo_url_changed(self, user):
+        self.__update_icon_url()
+    
+    def __user_aims_changed(self, user):
+        self.__update_aims()
+    
+    def __user_emails_changed(self, user):
+        self.__update_emails()
+    
+    def __user_xmpps_changed(self, user):
+        self.__update_xmpps()
+    
+    def __user_aim_buddies_changed(self, user):
+        self.__update_aim_buddies()
+    
+    def __user_xmpp_buddies_changed(self, user):
+        self.__update_xmpp_buddies()
+    
+    def __user_local_buddies_changed(self, user):
+        self.__update_local_buddies()
 
-        #_logger.debug("photo url now %s" % str(new_icon_url))
+    ##############################################################
 
-        if new_icon_url != self.icon_url:
-            self.icon_url = new_icon_url
-            self.emit("icon-url-changed")
-
-    def __refresh_icon_url(self):
-        new_icon_url = None
-        no_photo_url = None
-        if self.is_contact:
-            try:
-                new_icon_url = self.resource.user.photoUrl
-            except AttributeError:
-                pass
+    def __update_name(self):
+        name = None
+        if self.contact:
+            name = getattr(self.contact, 'name', None)
+        if not name and self.user:
+            name = getattr(self.user, 'name', None)
+        if not name:
+            for buddy in self.aim_buddies:
+                name = getattr(buddy, 'alias', None)
+                if name:
+                    break
+        if not name:
+            for buddy in self.xmpp_buddies:
+                name = getattr(buddy, 'alias', None)
+                if name:
+                    break
+        if not name:
+            for buddy in self.local_buddies:
+                name = getattr(buddy, 'alias', None)
+                if name:
+                    break
+        if not name:
+            for buddy in self.aim_buddies:
+                name = getattr(buddy, 'name', None)
+                if name:
+                    break
+        if not name:
+            for buddy in self.xmpp_buddies:
+                name = getattr(buddy, 'name', None)
+                if name:
+                    break
+        if not name:
+            for buddy in self.local_buddies:
+                name = getattr(buddy, 'name', None)
+                if name:
+                    break
+        if not name and len(self.emails) > 0:
+            name = self.emails[0]
 
-            ## see if we can get an icon from one of our buddies 
-            try:
-                no_photo_url = self.resource.model.global_resource.fallbackUserPhotoUrl
-            except AttributeError, e:
-                pass
-
-            if not new_icon_url or new_icon_url == no_photo_url:
-                try:
-                    new_icon_url = self.xmpp_buddy.icon
-                except AttributeError:
-                    pass
-
-            if not new_icon_url or new_icon_url == no_photo_url:
-                try:
-                    new_icon_url = self.aim_buddy.icon
-                except AttributeError:
-                    pass
+        if name == None:
+            name = "NO_NAME"
 
-        else:
-            try:
-                new_icon_url = self.resource.icon
-            except AttributeError:
-                pass
+        if self.display_name != name:
+            self.display_name = name
+            self.emit('display-name-changed')
+
+    def __update_icon_url(self):
+        icon_url = None
+        if self.user:
+            icon_url = getattr(self.user, 'photoUrl', None)
+            
+        if not icon_url:
+            for buddy in self.aim_buddies:
+                icon_url = getattr(buddy, 'icon', None)
+                if icon_url:
+                    break
+        if not icon_url:
+            for buddy in self.xmpp_buddies:
+                icon_url = getattr(buddy, 'icon', None)
+                if icon_url:
+                    break
+        if not icon_url:
+            for buddy in self.local_buddies:
+                icon_url = getattr(buddy, 'contact', None)
+                if icon_url:
+                    break
+    
+        if self.icon_url != icon_url:
+            self.icon_url = icon_url
+            self.emit('icon-url-changed')
+
+    def __update_aims(self):
+        aims = set()
+        if self.contact:
+            aims.update(getattr(self.contact, 'aims', []))
+        if self.user:
+            aims.update(getattr(self.user, 'aims', []))
+        if self.buddy and self.buddy.protocol == 'aim':
+            aims.add(self.buddy.name)
+
+        aims = list(aims)
+        aims.sort()
+        
+        if self.aims != aims:
+            self.aims = aims
+            self.emit('aims-changed')
+            
+    def __update_emails(self):
+        emails = set()
+        if self.contact:
+            emails.update(getattr(self.contact, 'emails', []))
+        if self.user:
+            emails.update(getattr(self.user, 'emails', []))
+
+        emails = list(emails)
+        emails.sort()
+        
+        if self.emails != emails:
+            self.emails = emails
+            self.emit('emails-changed')
+            
+        self.__update_name()
+        
+    def __update_xmpps(self):
+        xmpps = set()
+        if self.contact:
+            xmpps.update(getattr(self.contact, 'xmpps', []))
+        if self.user:
+            xmpps.update(getattr(self.user, 'xmpps', []))
+        if self.buddy and self.buddy.protocol == 'xmpp':
+            xmpps.add(self.buddy.name)
 
-        self.__set_icon_url(new_icon_url)
+        xmpps = list(xmpps)
+        xmpps.sort()
+    
+        if self.xmpps != xmpps:
+            self.xmpps = xmpps
+            self.emit('xmpps-changed')
             
-    def __user_photo_url_changed(self, user_resource):
-        self.__refresh_icon_url()
+    def __update_aim_buddies(self):
+        aim_buddies = set()
+        if self.contact:
+            aim_buddies.update(getattr(self.contact, 'aimBuddies', []))
+        if self.user:
+            aim_buddies.update(getattr(self.user, 'aimBuddies', []))
+        if self.buddy and self.buddy.protocol == 'aim':
+            aim_buddies.add(self.buddy)
 
-    def __buddy_alias_changed(self, resource):
-        try:
-            self.display_name = resource.alias
-        except AttributeError:
-            self.display_name = None
+        aim_buddies = list(aim_buddies)
+        aim_buddies.sort()
+    
+        if self.aim_buddies != aim_buddies:
+            self.aim_buddies = aim_buddies
+            self.emit('aim-buddies-changed')
+
+        self.__update_name()
+        self.__update_icon_url()
+        self.__update_online()
+            
+    def __update_xmpp_buddies(self):
+        xmpp_buddies = set()
+        if self.contact:
+            xmpp_buddies.update(getattr(self.contact, 'xmppBuddies', []))
+        if self.user:
+            xmpp_buddies.update(getattr(self.user, 'xmppBuddies', []))
+        if self.buddy and self.buddy.protocol == 'xmpp':
+            xmpp_buddies.add(self.buddy)
 
-        if self.display_name == None:
-            if resource.protocol == 'mugshot-local':
-                ## resource.name for this would be a data model URI thing
-                self.display_name = 'NO_NAME'
-            else:
-                ## resource.name for xmpp/aim should be the xmpp/aim address
-                self.display_name = resource.name
-
-        self.emit("display-name-changed")
-
-    def __buddy_icon_changed(self, resource):
-        self.__refresh_icon_url()
-
-    def __buddy_online_changed(self, resource):
-        if resource.isOnline != self.online:
-            self.online = resource.isOnline
+        xmpp_buddies = list(xmpp_buddies)
+        xmpp_buddies.sort()
+    
+        if self.xmpp_buddies != xmpp_buddies:
+            self.xmpp_buddies = xmpp_buddies
+            self.emit('xmpp-buddies-changed')
+            
+        self.__update_name()
+        self.__update_icon_url()
+        self.__update_online()
+            
+    def __update_local_buddies(self):
+        local_buddies = set()
+        if self.contact:
+            local_buddies.update(getattr(self.contact, 'mugshotLocalBuddies', []))
+        if self.user:
+            local_buddies.update(getattr(self.user, 'mugshotLocalBuddies', []))
+        if self.buddy and self.buddy.protocol == 'mugshot-local':
+            local_buddies.add(self.buddy)
+
+        local_buddies = list(local_buddies)
+        local_buddies.sort()
+
+        if self.local_buddies != local_buddies:
+            self.local_buddies = local_buddies
+            self.emit('local-buddies-changed')
+            
+        self.__update_name()
+        self.__update_icon_url()
+        self.__update_online()
+            
+    def __update_icon_url(self):
+        icon_url = None
+        if self.user:
+            icon_url = getattr(self.user, 'photoUrl', None)
+            
+        if not icon_url:
+            for buddy in self.aim_buddies:
+                icon_url = getattr(buddy, 'icon', None)
+                if icon_url:
+                    break
+        if not icon_url:
+            for buddy in self.xmpp_buddies:
+                icon_url = getattr(buddy, 'icon', None)
+                if icon_url:
+                    break
+        if not icon_url:
+            for buddy in self.local_buddies:
+                icon_url = getattr(buddy, 'contact', None)
+                if icon_url:
+                    break
+    
+        if self.icon_url != icon_url:
+            self.icon_url = icon_url
+            self.emit('icon-url-changed')
+ 
+    def __update_online(self):
+        online = False
+        if not online:
+            for buddy in self.aim_buddies:
+                online = buddy.isOnline
+                if online:
+                    break
+        if not online:
+            for buddy in self.xmpp_buddies:
+                online = buddy.isOnline
+                if online:
+                    break
+        if not online:
+            for buddy in self.local_buddies:
+                online = buddy.isOnline
+                if online:
+                    break
+    
+        if self.online != online:
+            self.online = online
             self.emit('online-changed')
 
+    def __update_status(self):
+        status = STATUS_NOT_A_CONTACT
+        if self.contact:
+            status = self.contact.status
+            
+        if self.status != status:
+            self.status = status
+            self.emit('status-changed')
+
     def __hash__(self):
         return hash(self.resource)
 
@@ -359,10 +460,15 @@
         for resource in self.__resources:
             if resource.class_id == "online-desktop:/p/o/buddy" and resource not in resources:
                 resource.disconnect(self.__buddy_user_changed)
+                resource.disconnect(self.__buddy_contact_changed)
+                # FIXME: we need to watch for changes on user.contact as well, which will
+                # require some rewriting; currently we won't update properly when someone
+                # adds a resource on the online.gnome.org server
 
         for resource in resources:
             if resource.class_id == "online-desktop:/p/o/buddy" and resource not in self.__resources:
                 resource.connect(self.__buddy_user_changed, "user")
+                resource.connect(self.__buddy_contact_changed, "contact")
 
         self.__resources = resources
         self.__update_resolved()
@@ -372,19 +478,22 @@
 
         for resource in self.__resources:
             if resource.class_id == "online-desktop:/p/o/buddy":
-                try:
-                    user = resource.user
-                except AttributeError:
-                    user = None
+                user = getattr(resource, 'user', None)
+                contact = getattr(resource, 'contact', None)
+
+                if user != None and contact == None:
+                    contact = getattr(user, 'contact', None)
                     
-                if user != None:
+                if contact != None:
+                    resource = contact
+                elif user != None:
                     resource = user
 
             try:
                 person = resource.__person
             except AttributeError:
                 person = resource.__person = Person(resource)
-                
+
             resolved.add(person)
 
         old_resolved = self.__resolved
@@ -401,6 +510,9 @@
     def __buddy_user_changed(self, resource):
         self.__update_resolved()
 
+    def __buddy_contact_changed(self, resource):
+        self.__update_resolved()
+
     def __str__(self):
         return self.__resolved.__str__()
 
@@ -438,139 +550,6 @@
     def __iter__(self):
         return self.__items.itervalues()
 
-## used to allow some resources in a person set to hide others
-class RemoveDuplicatesPersonSet(PersonSet):
-    def __init__(self, source):
-        PersonSet.__init__(self)
-        self.__source = source
-        self.__source.connect('added', self.__on_added)
-        self.__source.connect('removed', self.__on_removed)
-
-        self.__not_included = set()
-        self.__included = set()
-
-        for item in self.__source:
-            self.__on_added(self, self.__source, item)
-
-        self.__connections = gutil.DisconnectSet()
-
-    def __on_added(self, source, item):
-
-        ## monitor properties that affect what hides what
-        self.__connections.add(item, item.connect('xmpp-buddy-changed', self.__on_changed))
-        self.__connections.add(item, item.connect('aim-buddy-changed', self.__on_changed))
-
-        ## if we are hidden by anything included, then 
-        ## we are not included
-        is_hidden = False
-        for included in self.__included:
-            if self.__is_hidden_by(item, included):
-                is_hidden = True
-                break
-
-        if is_hidden:
-            self.__not_included.add(item)
-        else:
-            ## if we are not hidden, then see if we hide something 
-            ## else and add ourselves to the included set
-
-            items_we_hide = []
-            for included in self.__included:
-                if self.__is_hidden_by(included, item):
-                    items_we_hide.append(included)
-
-            self.__included.add(item)
-            self.emit('added', item)
-
-            self.__consider_including_items(items_we_hide)
-
-    def __on_removed(self, source, item):
-
-        self.__connections.disconnect_object(item)
-
-        ## if we weren't included anyhow, nothing to do
-        if item in self.__not_included:
-            self.__not_included.remove(item)
-            return
-
-        if item in self.__included:
-            self.__included.remove(item)
-            self.emit('removed', item)
-            
-            ## if we were included, we might have been hiding
-            ## something else, so we need to see if anything in 
-            ## not_included is now included
-            self.__consider_including_items(self.__not_included)
-
-    def __on_changed(self, item):
-        ## if we changed, we may no longer be hiding another item, 
-        ## or may no longer be hidden by another item, or we may 
-        ## now hide another item, or may now be hidden by another 
-        ## item
-
-        # this should result in un-hiding either other items or 
-        # the changed item, if appropriate
-        self.__consider_including_items(self.__not_included)
-        
-        # this should result in hiding either other items or 
-        # the changed item, if appropriate
-        self.__consider_including_items(self.__included)        
-
-    def __consider_including_items(self, maybe_now_included):
-        ## FIXME this is messed up since self.__included is
-        ## used to compute what should be in self.__included ...
-
-        # since maybe_now_included may be self.__included or self.__not_included, 
-        # we can't remove or add while iterating over it
-        to_add = set()
-        to_remove = set()
-
-        for maybe_included in maybe_now_included:
-            include = True
-            for may_hide in self.__included:
-                if self.__is_hidden_by(maybe_included, may_hide):
-                    include = False
-                    break
-
-            if include:
-                to_add.add(maybe_included)
-            else:
-                to_remove.add(maybe_included)
-
-        for person in to_add:
-            if person not in self.__included:
-                self.__not_included.remove(person)
-                self.__included.add(person)
-                self.emit('added', person)
-            
-        for person in to_remove:
-            if person in self.__included:
-                self.__included.remove(person)
-                self.__not_included.add(person)
-                self.emit('removed', person)
-
-    def __is_hidden_by(self, hidden, hider):
-        ## the idea is that contacts hide IM buddies they correspond
-        ## to (buddies are merged into contacts)
-
-        if hidden == hider:
-            return False
-
-        if hider.is_contact and \
-                (hidden.resource == hider.aim_buddy or \
-                     hidden.resource == hider.xmpp_buddy or \
-                     hidden.resource == hider.local_buddy):
-            return True
-        else:
-            return False
-
-    def __str__(self):
-        return str(self.__included)
-
-    def __iter__(self):
-        return self.__included.__iter__()
-
-
 class PeopleTracker(Singleton):
     """Singleton object for tracking available users and contacts
 
@@ -591,7 +570,7 @@
         self.xmpp_people = SinglePersonSet()
         self.local_people = SinglePersonSet()
         
-        self.people = RemoveDuplicatesPersonSet(UnionPersonSet(self.contacts, self.aim_people, self.xmpp_people, self.local_people))
+        self.people = UnionPersonSet(self.contacts, self.aim_people, self.xmpp_people, self.local_people)
 
         if self.__model.ready:
             self.__on_ready()
@@ -601,15 +580,16 @@
         # When we disconnect from the server we freeze existing content, then on reconnect
         # we clear everything and start over.
 
-        contact_props = '[+;name;user [+;photoUrl;mugshotLocalBuddy];aims;aimBuddies [+;icon;statusMessage];mugshotLocalBuddies [+;icon;user];xmpps;xmppBuddies [+;icon;statusMessage];emails;status]'
+        contact_props = '[+;name;user [+;aims;aimBuddies [+;icon;statusMessage];xmpps;xmppBuddies [+;icon;statusMessage];mugshotLocalBuddies  [+;icon;statusMessage]];emails;aims;aimBuddies [+;icon;statusMessage];mugshotLocalBuddies [+;icon];xmpps;xmppBuddies [+;icon;statusMessage];emails;status]'
         
         if self.__model.self_resource != None:
             query = self.__model.query_resource(self.__model.self_resource, "contacts %s" % contact_props)
             query.add_handler(self.__on_got_self)
             query.execute()
 
+        # Since we've previously retrieved all our contacts with a bunch of properties, we can just use 'contact +' properties
         query = self.__model.query_resource(self.__model.global_resource,
-                                            "aimBuddies [+;icon;statusMessage;contact %s]; xmppBuddies [+;icon;statusMessage;contact %s]; mugshotLocalBuddies [+;icon;user;contact %s]" % (contact_props, contact_props, contact_props))
+                                            "aimBuddies [+;icon;statusMessage;contact +;user [+;contact]]; xmppBuddies [+;icon;statusMessage;contact +;user [+;contact]]; mugshotLocalBuddies [+;icon;user;user [+;contact]]")
 
         query.add_handler(self.__on_got_global)
         query.execute()
@@ -684,37 +664,30 @@
 RANK_HOT_ONLINE = 12
 
 def __get_raw_contact_rank(contact):
-    try:
-        status = contact.resource.status
-    except AttributeError:
-        status = STATUS_NOT_A_CONTACT
-
-    if status == STATUS_NOT_A_CONTACT:
+    if contact.status == STATUS_NOT_A_CONTACT:
         return RANK_COLD
-    elif status == STATUS_BLOCKED:
+    elif contact.status == STATUS_BLOCKED:
         return RANK_NO_DISPLAY
 
     if contact.online:
-        if status == STATUS_MEDIUM:
+        if contact.status == STATUS_MEDIUM:
             return RANK_MEDIUM_ONLINE
-        elif status == STATUS_HOT:
+        elif contact.status == STATUS_HOT:
             return RANK_HOT_ONLINE
     else:
-        if status == STATUS_MEDIUM:
+        if contact.status == STATUS_MEDIUM:
             return RANK_MEDIUM_OFFLINE
-        elif status == STATUS_HOT:
+        elif contact.status == STATUS_HOT:
             return RANK_HOT_OFFLINE
 
     ## I believe this is not reached
     return RANK_COLD
 
 def __get_contact_rank(contact):
-    ## subtract 1 if not a user
     rank = __get_raw_contact_rank(contact)
-    try:
-        user = contact.resource.user
-    except AttributeError:
-        rank = rank - 1
+    ## subtract 1 if not a user
+    if not contact.user:
+        rank -= 1
 
     return rank
         
@@ -744,12 +717,12 @@
     rankA = 0
     rankB = 0
 
-    if a.is_contact:
+    if a.contact:
         rankA = __get_contact_rank(a)
     else:
         rankA = __get_buddy_rank(a)
 
-    if b.is_contact:
+    if b.contact:
         rankB = __get_contact_rank(b)
     else:
         rankB = __get_buddy_rank(b)

Modified: trunk/bigboard/stocks/people/PeopleStock.py
==============================================================================
--- trunk/bigboard/stocks/people/PeopleStock.py	(original)
+++ trunk/bigboard/stocks/people/PeopleStock.py	Tue Jan 29 22:55:57 2008
@@ -75,9 +75,7 @@
             box.remove(item)
             box.insert_sorted(item, hippo.PACK_IF_FITS, lambda a,b: sort_people(a.person, b.person))
 
-        if person.is_contact:
-            person.resource.connect(resort, 'status')
-
+        person.connect('status-changed', resort)
         person.connect('online-changed', resort)
         person.connect('display-name-changed', resort)
         
@@ -119,20 +117,24 @@
         self.__slideout_item = item
 
         coords = item.get_screen_coords()
-        if not self.__slideout.slideout_from(coords[0] + item.get_allocation()[0] + 4, coords[1]):
-            self.__close_slideout()
-            return
 
         p = ProfileItem(item.get_person(),
                         themed=True,
                         border=1,
                         border_color = 0x0000000ff)
-
+        
         self.__slideout.get_root().append(p)
         p.connect("close", self.__close_slideout)
         self.__slideout.connect("close", self.__close_slideout)
 
-        return True
+        try:
+            success = False
+            success = self.__slideout.slideout_from(coords[0] + item.get_allocation()[0] + 4, coords[1])
+        finally:
+            if not success:
+                self.__close_slideout()
+
+        return success
 
     def __on_more_button(self):
         if self.__people_browser is None:
@@ -167,13 +169,8 @@
 
     def _on_activated(self):
         """Action when user has activated the result"""
-        if self.__person.is_contact:
-            try:
-                user = self.__person.resource.user
-            except AttributeError:
-                user = None
-            if user:
-                libbig.show_url(user.homeUrl)
+        if self.person.user:
+            libbig.show_url(self.person.user)
         else:
             ### FIXME - what should we do here? open an IM conversation?
             ### or scroll to and pop out the user in the people stock?
@@ -197,22 +194,31 @@
             if query in p.display_name:
                 matched = True
 
-            if p.is_contact and not matched:
+            if p.contact and not matched:
+                emails = []
+                try:
+                    for email in getattr(p.contact.emails):
+                        if query in email.lower():
+                            matched = True
+                            break
+                except AttributeError:
+                    pass
+                
+            if p.user and not matched:
                 emails = []
                 try:
-                    emails = p.resource.emails
+                    for email in getattr(p.user.emails):
+                        if query in email.lower():
+                            matched = True
+                            break
                 except AttributeError:
                     pass
-                for email in emails:
-                    if query in email.lower():
-                        matched = True
-                        break
             
             if not matched:
-                if p.aim and query in p.aim:
+                if query in p.aims:
                     matched = True
                 
-                if p.xmpp and query in p.xmpp:
+                if query in p.xmpps:
                     matched = True
 
             if matched:

Modified: trunk/bigboard/stocks/people/peoplebrowser.py
==============================================================================
--- trunk/bigboard/stocks/people/peoplebrowser.py	(original)
+++ trunk/bigboard/stocks/people/peoplebrowser.py	Tue Jan 29 22:55:57 2008
@@ -63,9 +63,8 @@
             self.remove(item)
             self.add_column_item(section, item, lambda a,b: sort_people(a.person, b.person))
 
-        if person.is_contact:
-            person.resource.connect(resort, 'status')
         person.connect('display-name-changed', resort)
+        person.connect('status-changed', resort)
         
         self.__update_visibility(section, item)
 

Modified: trunk/bigboard/stocks/people/peoplewidgets.py
==============================================================================
--- trunk/bigboard/stocks/people/peoplewidgets.py	(original)
+++ trunk/bigboard/stocks/people/peoplewidgets.py	Tue Jan 29 22:55:57 2008
@@ -118,25 +118,20 @@
         self.__current_track = None
         self.__current_track_timeout = None
 
-        if self.person.is_contact:
-            try:
-                user = self.person.resource.user
-            except AttributeError:
-                user = None
-
-            if user:
-                query = model.query_resource(user, "currentTrack +;currentTrackPlayTime")
-                query.execute()
-
-                user.connect(self.__update_current_track, 'currentTrack')
-                user.connect(self.__update_current_track, 'currentTrackPlayTime')
-                self.__update_current_track(user)
+        user = self.person.user
+        if user:
+            query = model.query_resource(user, "currentTrack +;currentTrackPlayTime")
+            query.execute()
+
+            user.connect(self.__update_current_track, 'currentTrack')
+            user.connect(self.__update_current_track, 'currentTrackPlayTime')
+            self.__update_current_track(user)
             
         self.person.connect('display-name-changed', self.__update)
         self.person.connect('icon-url-changed', self.__update)
         
-        self.person.connect('aim-buddy-changed', self.__update_aim_buddy)
-        self.person.connect('xmpp-buddy-changed', self.__update_xmpp_buddy)
+        self.person.connect('aim-buddies-changed', self.__update_aim_buddy)
+        self.person.connect('xmpp-buddies-changed', self.__update_xmpp_buddy)
         
         self.__update(self.person)
         self.__update_aim_buddy(self.person)
@@ -191,9 +186,9 @@
             self.__set_status(STATUS_IM, sm)
 
     def __update_aim_buddy(self, person):
-        if person.aim_buddy:
+        if len(person.aim_buddies) > 0:
             if not self.__aim_icon:
-                self.__aim_icon = AimIcon(person.aim_buddy, theme_hints=(not self.__themed and 'notheme' or []))
+                self.__aim_icon = AimIcon(person.aim_buddies[0], theme_hints=(not self.__themed and 'notheme' or []))
                 self.__presence_box.append(self.__aim_icon)
         else:
             if self.__aim_icon:
@@ -203,9 +198,9 @@
         self.__reset_im_status()
 
     def __update_xmpp_buddy(self, person):
-        if person.xmpp_buddy:
+        if len(person.xmpp_buddies) > 0:
             if not self.__xmpp_icon:
-                self.__xmpp_icon = XMPPIcon(person.xmpp_buddy, theme_hints=(not self.__themed and 'notheme' or []))
+                self.__xmpp_icon = XMPPIcon(person.xmpp_buddies[0], theme_hints=(not self.__themed and 'notheme' or []))
                 self.__presence_box.append(self.__xmpp_icon)
         else:
             if self.__xmpp_icon:
@@ -216,7 +211,7 @@
 
     def __timeout_track(self):
         self.__current_track_timeout = None
-        self.__update_current_track(self.person.resource.user)
+        self.__update_current_track(self.person.user)
         return False
 
     def __update_current_track(self, user):
@@ -532,15 +527,11 @@
 
         self.__header.append(name_vbox)
 
-        if person.is_contact:
-            try:
-                user = person.resource.user
-            except AttributeError:
-                user = None
-            if user:
-                mugshot_link = linkklass(text="Mugshot", padding=6)
-                self.__header.append(mugshot_link, flags=hippo.PACK_END)
-                mugshot_link.connect("activated", self.__on_activate_web)
+        user = self.person.user
+        if user:
+            mugshot_link = linkklass(text="Mugshot", padding=6)
+            self.__header.append(mugshot_link, flags=hippo.PACK_END)
+            mugshot_link.connect("activated", self.__on_activate_web)
 
         self.__top_box = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
         self.append(self.__top_box)
@@ -548,15 +539,10 @@
         self.__photo = CanvasMugshotURLImage(scale_width=60,
                                             scale_height=60,
                                             border=5)
-
-        if person.is_contact:
-            try:
-                user = person.resource.user
-            except AttributeError:
-                user = None
-            if user:
-                self.__photo.set_clickable(True)
-                self.__photo.connect("activated", self.__on_activate_web)
+        
+        if user:
+            self.__photo.set_clickable(True)
+            self.__photo.connect("activated", self.__on_activate_web)
 
         self.__top_box.append(self.__photo)
 
@@ -567,7 +553,7 @@
                                                     spacing=4, border=4)
         self.append(self.__contact_status_box)
 
-        if person.is_contact:
+        if person.contact:
             self.__add_link = None
             self.__remove_link = linkklass()
             self.__remove_link.connect('activated', self.__remove_from_network_clicked)
@@ -596,37 +582,26 @@
 
         self.person.connect('display-name-changed', self.__update)
         self.person.connect('icon-url-changed', self.__update)
-        self.person.connect('aim-changed', self.__update)
-        self.person.connect('local-buddy-changed', self.__update_local_buddy)
-        self.person.connect('xmpp-changed', self.__update)
-        if person.is_contact:
-            self.person.resource.connect(lambda *args: self.__update(self.person), 'emails')
-            self.person.resource.connect(self.__update_contact_status, "status")
-            try:
-                user = person.resource.user
-            except AttributeError:
-                user = None
-            
-            if user:
-                user.connect(self.__update_loved_accounts, "lovedAccounts")
-        
-        query = DataModel(bigboard.globals.server_name).query_resource(self.person.resource, "lovedAccounts +")
-        query.add_handler(self.__update_loved_accounts)
-        query.execute()
+        self.person.connect('aims-changed', self.__update)
+        self.person.connect('local-buddies-changed', self.__update_local_buddy)
+        self.person.connect('xmpps-changed', self.__update)
+        self.person.connect('emails-changed', self.__update)
+        self.person.connect('status-changed', self.__update_contact_status)
+
+        # FIXME - self.person.user can appear and go away
+        if self.person.user:
+            self.person.user.connect(self.__update_loved_accounts, "lovedAccounts")
+
+            query = DataModel(bigboard.globals.server_name).query_resource(self.person.user, "lovedAccounts +")
+            query.add_handler(self.__update_loved_accounts)
+            query.execute()
 
         self.__update(self.person)
         self.__update_local_buddy(self.person)
-        
-        if self.person.is_contact:
-            self.__update_contact_status(self.person.resource)
+        self.__update_contact_status(self.person)
 
-            try:
-                user = person.resource.user
-            except AttributeError:
-                user = None
-            
-            if user:
-                self.__update_loved_accounts(user)
+        if self.person.user:
+            self.__update_loved_accounts(self.person.user)
 
     def __add_status_link(self, text, current_status, new_status):
         textklass = self.__themed and ThemedText or hippo.CanvasText        
@@ -637,7 +612,7 @@
             def set_new_status(object):
                 model = globals.get_data_model()
                 query = model.update(("http://mugshot.org/p/contacts";, "setContactStatus"),
-                                     contact=self.person.resource,
+                                     contact=self.person.contact,
                                      status=new_status)
                 query.execute()
         
@@ -661,7 +636,7 @@
 
                 model = globals.get_data_model()
                 query = model.update(("http://mugshot.org/p/contacts";, "deleteContact"),
-                                     contact=person.resource)
+                                     contact=person.contact)
                 query.execute()
 
             else:
@@ -702,7 +677,7 @@
 
                 model = globals.get_data_model()
                 query = model.update(("http://mugshot.org/p/contacts";, "setContactName"),
-                                     contact=person.resource, name=name)
+                                     contact=person.contact, name=name)
                 query.execute()
 
             else:
@@ -733,10 +708,10 @@
         query.execute()
 
     def __add_to_network_clicked(self, link):
-        if self.person.aim:
-            self.__create_contact('aim', self.person.aim)
-        elif self.person.xmpp:
-            self.__create_contact('xmpp', self.person.xmpp)
+        if self.person.aims:
+            self.__create_contact('aim', self.person.aims[0])
+        elif self.person.xmpps:
+            self.__create_contact('xmpp', self.person.xmpp[0])
         elif self.person.local_buddy:
             self.__create_user_contact(self.person.local_buddy.user)
 
@@ -745,11 +720,8 @@
 
     def __update_contact_status(self, person):
         self.__contact_status_box.remove_all()
-        try:
-            status = self.person.resource.status
-        except AttributeError:
-            status = 0
-
+        
+        status = person.status
         if status == 0:
             status = 3 
 
@@ -762,7 +734,7 @@
 
     def __update_loved_accounts(self, person):
         try:
-            accounts = self.person.resource.lovedAccounts
+            accounts = self.person.user.lovedAccounts
         except AttributeError:
             accounts = []
         
@@ -776,12 +748,9 @@
             self.__local_files_link.destroy()
             self.__local_files_link = None
 
-        try:
-            buddy = person.local_buddy
-        except AttributeError:
-            return
-
-        if buddy != None:
+        if len(person.local_buddies) > 0:
+            buddy = person.local_buddies[0]
+            
             self.__local_files_link = LocalFilesLink(buddy)
             self.__link_box.append(self.__local_files_link)
 
@@ -798,25 +767,18 @@
             self.__remove_link.set_property('text', 
                                             "Remove %s from network" % self.person.display_name)
 
-        emails = None
-        if person.is_contact:
-            try:
-                emails = self.person.resource.emails
-            except AttributeError:
-                pass
-         
-        if emails != None and len(emails) > 0:
-            email = linkklass(text=emails[0], xalign=hippo.ALIGNMENT_START)
+        if len(person.emails) > 0:
+            email = linkklass(text=person.emails[0], xalign=hippo.ALIGNMENT_START)
             email.connect('activated', self.__on_activate_email)
             self.__address_box.append(email)
 
-        if person.aim != None:
-            aim = linkklass(text=person.aim, xalign=hippo.ALIGNMENT_START)
+        if len(person.aims) > 0:
+            aim = linkklass(text=person.aims[0], xalign=hippo.ALIGNMENT_START)
             aim.connect('activated', self.__on_activate_aim)
             self.__address_box.append(aim)
 
-        if person.xmpp != None:
-            xmpp = linkklass(text=person.aim, xalign=hippo.ALIGNMENT_START)
+        if len(person.xmpps) > 0:
+            xmpp = linkklass(text=person.xmpps[0], xalign=hippo.ALIGNMENT_START)
             xmpp.connect('activated', self.__on_activate_xmpp)
             self.__address_box.append(xmpp)
 
@@ -826,20 +788,20 @@
 
     def __on_activate_web(self, canvas_item):
         self.emit("close", True)
-        libbig.show_url(self.person.resource.user.homeUrl)
+        libbig.show_url(self.person.user.homeUrl)
 
     def __on_activate_email(self, canvas_item):
         self.emit("close", True)
         # email should probably cgi.escape except it breaks if you escape the @
-        os.spawnlp(os.P_NOWAIT, 'gnome-open', 'gnome-open', 'mailto:' + self.person.resource.email)
+        os.spawnlp(os.P_NOWAIT, 'gnome-open', 'gnome-open', 'mailto:' + self.person.emails[0])
 
     def __on_activate_aim(self, canvas_item):
         self.emit("close", True)
-        _open_aim(self.person.aim)
+        _open_aim(self.person.aims[0])
         
     def __on_activate_xmpp(self, canvas_item):
         self.emit("close", True)
-        _open_xmpp(self.person.xmpp)
+        _open_xmpp(self.person.xmpps[0])
 
     def __on_activate_add_address(self, canvas_item):
         dialog = gtk.Dialog(title=("Add an address for %s" % self.person.display_name))
@@ -881,7 +843,7 @@
             elif visible_type == 'AIM':
                 addressType = 'aim'
             elif visible_type == 'GTalk/XMPP':
-                addressType = xmpp
+                addressType = 'xmpp'
             else:
                 _logger.warn('Bug: unknown combox box text for address type')
                 if '@' in entry.get_text():
@@ -915,7 +877,7 @@
 
                 model = globals.get_data_model()
                 query = model.update(("http://mugshot.org/p/contacts";, "addContactAddress"),
-                                     contact=person.resource, addressType=addressType, address=address)
+                                     contact=person.contact, addressType=addressType, address=address)
                 query.execute()
 
             else:



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]