Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar-toolkit/src/sugar/presence
diff options
context:
space:
mode:
Diffstat (limited to 'sugar-toolkit/src/sugar/presence')
-rw-r--r--sugar-toolkit/src/sugar/presence/Makefile.am9
-rw-r--r--sugar-toolkit/src/sugar/presence/Makefile.in435
-rw-r--r--sugar-toolkit/src/sugar/presence/__init__.py24
-rw-r--r--sugar-toolkit/src/sugar/presence/activity.py410
-rw-r--r--sugar-toolkit/src/sugar/presence/buddy.py239
-rw-r--r--sugar-toolkit/src/sugar/presence/presenceservice.py609
-rw-r--r--sugar-toolkit/src/sugar/presence/sugartubeconn.py63
-rw-r--r--sugar-toolkit/src/sugar/presence/test_presence.txt26
-rw-r--r--sugar-toolkit/src/sugar/presence/tubeconn.py114
9 files changed, 1929 insertions, 0 deletions
diff --git a/sugar-toolkit/src/sugar/presence/Makefile.am b/sugar-toolkit/src/sugar/presence/Makefile.am
new file mode 100644
index 0000000..0c4368b
--- /dev/null
+++ b/sugar-toolkit/src/sugar/presence/Makefile.am
@@ -0,0 +1,9 @@
+sugardir = $(pythondir)/sugar/presence
+sugar_PYTHON = \
+ __init__.py \
+ activity.py \
+ buddy.py \
+ sugartubeconn.py \
+ tubeconn.py \
+ presenceservice.py
+
diff --git a/sugar-toolkit/src/sugar/presence/Makefile.in b/sugar-toolkit/src/sugar/presence/Makefile.in
new file mode 100644
index 0000000..2ec6716
--- /dev/null
+++ b/sugar-toolkit/src/sugar/presence/Makefile.in
@@ -0,0 +1,435 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/sugar/presence
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \
+ $(sugar_PYTHON)
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gnome-compiler-flags.m4 \
+ $(top_srcdir)/m4/intltool.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/python.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_CLEAN_FILES =
+SOURCES =
+DIST_SOURCES =
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
+am__installdirs = "$(DESTDIR)$(sugardir)"
+sugarPYTHON_INSTALL = $(INSTALL_DATA)
+py_compile = $(top_srcdir)/py-compile
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ALL_LINGUAS = @ALL_LINGUAS@
+AMTAR = @AMTAR@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+EXT_CFLAGS = @EXT_CFLAGS@
+EXT_LIBS = @EXT_LIBS@
+FGREP = @FGREP@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLLIBS = @INTLLIBS@
+INTLTOOL_CAVES_RULE = @INTLTOOL_CAVES_RULE@
+INTLTOOL_DESKTOP_RULE = @INTLTOOL_DESKTOP_RULE@
+INTLTOOL_DIRECTORY_RULE = @INTLTOOL_DIRECTORY_RULE@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_KBD_RULE = @INTLTOOL_KBD_RULE@
+INTLTOOL_KEYS_RULE = @INTLTOOL_KEYS_RULE@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_OAF_RULE = @INTLTOOL_OAF_RULE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_POLICY_RULE = @INTLTOOL_POLICY_RULE@
+INTLTOOL_PONG_RULE = @INTLTOOL_PONG_RULE@
+INTLTOOL_PROP_RULE = @INTLTOOL_PROP_RULE@
+INTLTOOL_SCHEMAS_RULE = @INTLTOOL_SCHEMAS_RULE@
+INTLTOOL_SERVER_RULE = @INTLTOOL_SERVER_RULE@
+INTLTOOL_SERVICE_RULE = @INTLTOOL_SERVICE_RULE@
+INTLTOOL_SHEET_RULE = @INTLTOOL_SHEET_RULE@
+INTLTOOL_SOUNDLIST_RULE = @INTLTOOL_SOUNDLIST_RULE@
+INTLTOOL_THEME_RULE = @INTLTOOL_THEME_RULE@
+INTLTOOL_UI_RULE = @INTLTOOL_UI_RULE@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_XAM_RULE = @INTLTOOL_XAM_RULE@
+INTLTOOL_XML_NOMERGE_RULE = @INTLTOOL_XML_NOMERGE_RULE@
+INTLTOOL_XML_RULE = @INTLTOOL_XML_RULE@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MSGFMT = @MSGFMT@
+MSGFMT_OPTS = @MSGFMT_OPTS@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+POFILES = @POFILES@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+PYGTK_CODEGEN = @PYGTK_CODEGEN@
+PYGTK_DEFSDIR = @PYGTK_DEFSDIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_INCLUDES = @PYTHON_INCLUDES@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WARN_CFLAGS = @WARN_CFLAGS@
+XGETTEXT = @XGETTEXT@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+lt_ECHO = @lt_ECHO@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+sugardir = $(pythondir)/sugar/presence
+sugar_PYTHON = \
+ __init__.py \
+ activity.py \
+ buddy.py \
+ sugartubeconn.py \
+ tubeconn.py \
+ presenceservice.py
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/sugar/presence/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/sugar/presence/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-sugarPYTHON: $(sugar_PYTHON)
+ @$(NORMAL_INSTALL)
+ test -z "$(sugardir)" || $(MKDIR_P) "$(DESTDIR)$(sugardir)"
+ @list='$(sugar_PYTHON)'; dlist=''; for p in $$list; do\
+ if test -f "$$p"; then b=; else b="$(srcdir)/"; fi; \
+ if test -f $$b$$p; then \
+ f=$(am__strip_dir) \
+ dlist="$$dlist $$f"; \
+ echo " $(sugarPYTHON_INSTALL) '$$b$$p' '$(DESTDIR)$(sugardir)/$$f'"; \
+ $(sugarPYTHON_INSTALL) "$$b$$p" "$(DESTDIR)$(sugardir)/$$f"; \
+ else :; fi; \
+ done; \
+ if test -n "$$dlist"; then \
+ if test -z "$(DESTDIR)"; then \
+ PYTHON=$(PYTHON) $(py_compile) --basedir "$(sugardir)" $$dlist; \
+ else \
+ PYTHON=$(PYTHON) $(py_compile) --destdir "$(DESTDIR)" --basedir "$(sugardir)" $$dlist; \
+ fi; \
+ else :; fi
+
+uninstall-sugarPYTHON:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sugar_PYTHON)'; dlist=''; for p in $$list; do\
+ f=$(am__strip_dir) \
+ rm -f "$(DESTDIR)$(sugardir)/$$f"; \
+ rm -f "$(DESTDIR)$(sugardir)/$${f}c"; \
+ rm -f "$(DESTDIR)$(sugardir)/$${f}o"; \
+ done
+tags: TAGS
+TAGS:
+
+ctags: CTAGS
+CTAGS:
+
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+ for dir in "$(DESTDIR)$(sugardir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am: install-sugarPYTHON
+
+install-dvi: install-dvi-am
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-info: install-info-am
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-ps: install-ps-am
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-sugarPYTHON
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+ distclean distclean-generic distclean-libtool distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ install-sugarPYTHON installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ uninstall uninstall-am uninstall-sugarPYTHON
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/sugar-toolkit/src/sugar/presence/__init__.py b/sugar-toolkit/src/sugar/presence/__init__.py
new file mode 100644
index 0000000..1136c19
--- /dev/null
+++ b/sugar-toolkit/src/sugar/presence/__init__.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""Client-code's interface to the PresenceService
+
+Provides a simplified API for accessing the dbus service
+which coordinates native network presence and sharing
+information. This includes both "buddies" and "shared
+activities".
+"""
diff --git a/sugar-toolkit/src/sugar/presence/activity.py b/sugar-toolkit/src/sugar/presence/activity.py
new file mode 100644
index 0000000..dc02aa1
--- /dev/null
+++ b/sugar-toolkit/src/sugar/presence/activity.py
@@ -0,0 +1,410 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""UI interface to an activity in the presence service
+
+STABLE.
+"""
+
+import logging
+
+import dbus
+import gobject
+import telepathy
+
+_logger = logging.getLogger('sugar.presence.activity')
+
+class Activity(gobject.GObject):
+ """UI interface for an Activity in the presence service
+
+ Activities in the presence service represent your and other user's
+ shared activities.
+
+ Properties:
+ id
+ color
+ name
+ type
+ joined
+ """
+ __gsignals__ = {
+ 'buddy-joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'buddy-left': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'new-channel': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
+ }
+
+ __gproperties__ = {
+ 'id' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'name' : (str, None, None, None, gobject.PARAM_READWRITE),
+ 'tags' : (str, None, None, None, gobject.PARAM_READWRITE),
+ 'color' : (str, None, None, None, gobject.PARAM_READWRITE),
+ 'type' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'private' : (bool, None, None, True, gobject.PARAM_READWRITE),
+ 'joined' : (bool, None, None, False, gobject.PARAM_READABLE),
+ }
+
+ _PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
+ _ACTIVITY_DBUS_INTERFACE = "org.laptop.Sugar.Presence.Activity"
+
+ def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
+ """Initialse the activity interface, connecting to service"""
+ gobject.GObject.__init__(self)
+ self._telepathy_room_handle = None
+ self._object_path = object_path
+ self._ps_new_object = new_obj_cb
+ self._ps_del_object = del_obj_cb
+ bobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
+ self._activity = dbus.Interface(bobj, self._ACTIVITY_DBUS_INTERFACE)
+ self._activity.connect_to_signal('BuddyHandleJoined',
+ self._buddy_handle_joined_cb)
+ self._activity.connect_to_signal('BuddyLeft',
+ self._buddy_left_cb)
+ self._activity.connect_to_signal('NewChannel', self._new_channel_cb)
+ self._activity.connect_to_signal('PropertiesChanged',
+ self._properties_changed_cb,
+ utf8_strings=True)
+ # FIXME: this *would* just use a normal proxy call, but I want the
+ # pending call object so I can block on it, and normal proxy methods
+ # don't return those as of dbus-python 0.82.1; so do it the hard way
+ self._get_properties_call = bus.call_async(self._PRESENCE_SERVICE,
+ object_path, self._ACTIVITY_DBUS_INTERFACE, 'GetProperties',
+ '', (), self._get_properties_reply_cb,
+ self._get_properties_error_cb, utf8_strings=True)
+
+ self._id = None
+ self._color = None
+ self._name = None
+ self._type = None
+ self._tags = None
+ self._private = True
+ self._joined = False
+ # Cache for get_buddy_by_handle, maps handles to buddy object paths
+ self._handle_to_buddy_path = {}
+ self._buddy_path_to_handle = {}
+
+ # Set up by set_up_tubes()
+ self.telepathy_conn = None
+ self.telepathy_tubes_chan = None
+ self.telepathy_text_chan = None
+ self._telepathy_room = None
+
+ def __repr__(self):
+ return ('<proxy for %s at %x>' % (self._object_path, id(self)))
+
+ def _get_properties_reply_cb(self, new_props):
+ self._get_properties_call = None
+ _logger.debug('%r: initial GetProperties returned', self)
+ self._properties_changed_cb(new_props)
+
+ def _get_properties_error_cb(self, e):
+ self._get_properties_call = None
+ # FIXME: do something with the error
+ _logger.warning('%r: Error doing initial GetProperties: %s', self, e)
+
+ def _properties_changed_cb(self, new_props):
+ _logger.debug('%r: Activity properties changed to %r', self, new_props)
+ val = new_props.get('name', self._name)
+ if isinstance(val, str) and val != self._name:
+ self._name = val
+ self.notify('name')
+ val = new_props.get('tags', self._tags)
+ if isinstance(val, str) and val != self._tags:
+ self._tags = val
+ self.notify('tags')
+ val = new_props.get('color', self._color)
+ if isinstance(val, str) and val != self._color:
+ self._color = val
+ self.notify('color')
+ val = bool(new_props.get('private', self._private))
+ if val != self._private:
+ self._private = val
+ self.notify('private')
+ val = new_props.get('id', self._id)
+ if isinstance(val, str) and self._id is None:
+ self._id = val
+ self.notify('id')
+ val = new_props.get('type', self._type)
+ if isinstance(val, str) and self._type is None:
+ self._type = val
+ self.notify('type')
+
+ def object_path(self):
+ """Get our dbus object path"""
+ return self._object_path
+
+ def do_get_property(self, pspec):
+ """Retrieve a particular property from our property dictionary"""
+
+ if pspec.name == "joined":
+ return self._joined
+
+ if self._get_properties_call is not None:
+ _logger.debug('%r: Blocking on GetProperties() because someone '
+ 'wants property %s', self, pspec.name)
+ self._get_properties_call.block()
+
+ if pspec.name == "id":
+ return self._id
+ elif pspec.name == "name":
+ return self._name
+ elif pspec.name == "color":
+ return self._color
+ elif pspec.name == "type":
+ return self._type
+ elif pspec.name == "tags":
+ return self._tags
+ elif pspec.name == "private":
+ return self._private
+
+ # FIXME: need an asynchronous API to set these properties, particularly
+ # 'private'
+ def do_set_property(self, pspec, val):
+ """Set a particular property in our property dictionary"""
+ if pspec.name == "name":
+ self._activity.SetProperties({'name': val})
+ self._name = val
+ elif pspec.name == "color":
+ self._activity.SetProperties({'color': val})
+ self._color = val
+ elif pspec.name == "tags":
+ self._activity.SetProperties({'tags': val})
+ self._tags = val
+ elif pspec.name == "private":
+ self._activity.SetProperties({'private': val})
+ self._private = val
+
+ def set_private(self, val, reply_handler, error_handler):
+ self._activity.SetProperties({'private': bool(val)},
+ reply_handler=reply_handler,
+ error_handler=error_handler)
+
+ def _emit_buddy_joined_signal(self, object_path):
+ """Generate buddy-joined GObject signal with presence Buddy object"""
+ self.emit('buddy-joined', self._ps_new_object(object_path))
+ return False
+
+ def _buddy_handle_joined_cb(self, object_path, handle):
+ _logger.debug('%r: buddy %s joined with handle %u', self, object_path,
+ handle)
+ gobject.idle_add(self._emit_buddy_joined_signal, object_path)
+ self._handle_to_buddy_path[handle] = object_path
+ self._buddy_path_to_handle[object_path] = handle
+
+ def _emit_buddy_left_signal(self, object_path):
+ """Generate buddy-left GObject signal with presence Buddy object
+
+ XXX note use of _ps_new_object instead of _ps_del_object here
+ """
+ self.emit('buddy-left', self._ps_new_object(object_path))
+ return False
+
+ def _buddy_left_cb(self, object_path):
+ _logger.debug('%r: buddy %s left', self, object_path)
+ gobject.idle_add(self._emit_buddy_left_signal, object_path)
+ handle = self._buddy_path_to_handle.pop(object_path, None)
+ if handle:
+ self._handle_to_buddy_path.pop(handle, None)
+
+ def _emit_new_channel_signal(self, object_path):
+ """Generate new-channel GObject signal with channel object path
+
+ New telepathy-python communications channel has been opened
+ """
+ self.emit('new-channel', object_path)
+ return False
+
+ def _new_channel_cb(self, object_path):
+ _logger.debug('%r: new channel created at %s', self, object_path)
+ gobject.idle_add(self._emit_new_channel_signal, object_path)
+
+ def get_joined_buddies(self):
+ """Retrieve the set of Buddy objects attached to this activity
+
+ returns list of presence Buddy objects that we can successfully
+ create from the buddy object paths that PS has for this activity.
+ """
+ resp = self._activity.GetJoinedBuddies()
+ buddies = []
+ for item in resp:
+ try:
+ buddies.append(self._ps_new_object(item))
+ except dbus.DBusException:
+ _logger.debug(
+ 'get_joined_buddies failed to get buddy object for %r',
+ item)
+ return buddies
+
+ def get_buddy_by_handle(self, handle):
+ """Retrieve the Buddy object given a telepathy handle.
+
+ buddy object paths are cached in self._handle_to_buddy_path,
+ so we can get the buddy without calling PS.
+ """
+ object_path = self._handle_to_buddy_path.get(handle, None)
+ if object_path:
+ buddy = self._ps_new_object(object_path)
+ return buddy
+ return None
+
+ def invite(self, buddy, message, response_cb):
+ """Invite the given buddy to join this activity.
+
+ The callback will be called with one parameter: None on success,
+ or an exception on failure.
+ """
+ op = buddy.object_path()
+ _logger.debug('%r: inviting %s', self, op)
+ self._activity.Invite(op, message,
+ reply_handler=lambda: response_cb(None),
+ error_handler=response_cb)
+
+ # Joining and sharing (FIXME: sharing is actually done elsewhere)
+
+ def set_up_tubes(self, reply_handler, error_handler):
+
+ chans = []
+
+ def tubes_ready():
+ if self.telepathy_text_chan is None or \
+ self.telepathy_tubes_chan is None:
+ return
+
+ _logger.debug('%r: finished setting up tubes', self)
+ reply_handler()
+
+ def tubes_chan_ready(chan):
+ _logger.debug('%r: Tubes channel %r is ready', self, chan)
+ self.telepathy_tubes_chan = chan
+ tubes_ready()
+
+ def text_chan_ready(chan):
+ _logger.debug('%r: Text channel %r is ready', self, chan)
+ self.telepathy_text_chan = chan
+ tubes_ready()
+
+ def conn_ready(conn):
+ _logger.debug('%r: Connection %r is ready', self, conn)
+ self.telepathy_conn = conn
+ found_text_channel = False
+ found_tubes_channel = False
+
+ for chan_path, chan_iface, handle_type, handle_ in chans:
+ if handle_type != telepathy.HANDLE_TYPE_ROOM:
+ return
+
+ if chan_iface == telepathy.CHANNEL_TYPE_TEXT:
+ telepathy.client.Channel(
+ conn.service_name, chan_path,
+ ready_handler=text_chan_ready,
+ error_handler=error_handler)
+ found_text_channel = True
+
+ elif chan_iface == telepathy.CHANNEL_TYPE_TUBES:
+ telepathy.client.Channel(
+ conn.service_name, chan_path,
+ ready_handler=tubes_chan_ready,
+ error_handler=error_handler)
+ found_tubes_channel = True
+
+ if not found_text_channel:
+ error_handler(AssertionError("Presence Service didn't create "
+ "a chatroom"))
+ elif not found_tubes_channel:
+ error_handler(AssertionError("Presence Service didn't create "
+ "tubes channel"))
+
+ def channels_listed(bus_name, conn_path, channels):
+ _logger.debug('%r: Connection on %s at %s, channels: %r',
+ self, bus_name, conn_path, channels)
+
+ # can't use assignment for this due to Python scoping
+ chans.extend(channels)
+
+ telepathy.client.Connection(bus_name, conn_path,
+ ready_handler=conn_ready,
+ error_handler=error_handler)
+
+
+ self._activity.ListChannels(reply_handler=channels_listed,
+ error_handler=error_handler)
+
+ def _join_cb(self):
+ _logger.debug('%r: Join finished', self)
+ self._joined = True
+ self.emit("joined", True, None)
+
+ def _join_error_cb(self, err):
+ _logger.debug('%r: Join failed because: %s', self, err)
+ self.emit("joined", False, str(err))
+
+ def join(self):
+ """Join this activity.
+
+ Emits 'joined' and otherwise does nothing if we're already joined.
+ """
+ if self._joined:
+ self.emit("joined", True, None)
+ return
+
+ _logger.debug('%r: joining', self)
+
+ def joined():
+ self.set_up_tubes(reply_handler=self._join_cb,
+ error_handler=self._join_error_cb)
+
+ self._activity.Join(reply_handler=joined,
+ error_handler=self._join_error_cb)
+
+ # GetChannels() wrapper
+
+ def get_channels(self):
+ """Retrieve communications channel descriptions for the activity
+
+ Returns a tuple containing:
+ - the D-Bus well-known service name of the connection
+ (FIXME: this is redundant; in Telepathy it can be derived
+ from that of the connection)
+ - the D-Bus object path of the connection
+ - a list of D-Bus object paths representing the channels
+ associated with this activity
+ """
+ (bus_name, connection, channels) = self._activity.GetChannels()
+ _logger.debug('%r: bus name is %s, connection is %s, channels are %r',
+ self, bus_name, connection, channels)
+ return bus_name, connection, channels
+
+ # Leaving
+
+ def _leave_cb(self):
+ """Callback for async action of leaving shared activity."""
+ self.emit("joined", False, "left activity")
+
+ def _leave_error_cb(self, err):
+ """Callback for error in async leaving of shared activity."""
+ _logger.debug('Failed to leave activity: %s', err)
+
+ def leave(self):
+ """Leave this shared activity"""
+ _logger.debug('%r: leaving', self)
+ self._joined = False
+ self._activity.Leave(reply_handler=self._leave_cb,
+ error_handler=self._leave_error_cb)
diff --git a/sugar-toolkit/src/sugar/presence/buddy.py b/sugar-toolkit/src/sugar/presence/buddy.py
new file mode 100644
index 0000000..fab23d2
--- /dev/null
+++ b/sugar-toolkit/src/sugar/presence/buddy.py
@@ -0,0 +1,239 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""UI interface to a buddy in the presence service
+
+STABLE.
+"""
+
+import gobject
+import gtk
+import dbus
+
+class Buddy(gobject.GObject):
+ """UI interface for a Buddy in the presence service
+
+ Each buddy interface tracks a set of activities and properties
+ that can be queried to provide UI controls for manipulating
+ the presence interface.
+
+ Properties Dictionary:
+ 'key': public key,
+ 'nick': nickname ,
+ 'color': color (XXX what format),
+ 'current-activity': (XXX dbus path?),
+ 'owner': (XXX dbus path?),
+ 'icon': (XXX pixel data for an icon?)
+ See __gproperties__
+ """
+ __gsignals__ = {
+ 'icon-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([])),
+ 'joined-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'left-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ }
+
+ __gproperties__ = {
+ 'key' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'icon' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'nick' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'color' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'current-activity' : (object, None, None, gobject.PARAM_READABLE),
+ 'owner' : (bool, None, None, False, gobject.PARAM_READABLE),
+ 'ip4-address' : (str, None, None, None, gobject.PARAM_READABLE)
+ }
+
+ _PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
+ _BUDDY_DBUS_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
+
+ def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
+ """Initialise the reference to the buddy
+
+ bus -- dbus bus object
+ new_obj_cb -- callback to call when this buddy joins an activity
+ del_obj_cb -- callback to call when this buddy leaves an activity
+ object_path -- path to the buddy object
+ """
+ gobject.GObject.__init__(self)
+ self._object_path = object_path
+ self._ps_new_object = new_obj_cb
+ self._ps_del_object = del_obj_cb
+ self._properties = {}
+ self._activities = {}
+
+ bobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
+ self._buddy = dbus.Interface(bobj, self._BUDDY_DBUS_INTERFACE)
+
+ self._icon_changed_signal = self._buddy.connect_to_signal(
+ 'IconChanged', self._icon_changed_cb, byte_arrays=True)
+ self._joined_activity_signal = self._buddy.connect_to_signal(
+ 'JoinedActivity', self._joined_activity_cb)
+ self._left_activity_signal = self._buddy.connect_to_signal(
+ 'LeftActivity', self._left_activity_cb)
+ self._property_changed_signal = self._buddy.connect_to_signal(
+ 'PropertyChanged', self._property_changed_cb)
+
+ self._properties = self._get_properties_helper()
+
+ activities = self._buddy.GetJoinedActivities()
+ for op in activities:
+ self._activities[op] = self._ps_new_object(op)
+ self._icon = None
+
+ def destroy(self):
+ self._icon_changed_signal.remove()
+ self._joined_activity_signal.remove()
+ self._left_activity_signal.remove()
+ self._property_changed_signal.remove()
+
+ def _get_properties_helper(self):
+ """Retrieve the Buddy's property dictionary from the service object
+ """
+ props = self._buddy.GetProperties(byte_arrays=True)
+ if not props:
+ return {}
+ return props
+
+ def do_get_property(self, pspec):
+ """Retrieve a particular property from our property dictionary
+
+ pspec -- XXX some sort of GTK specifier object with attributes
+ including 'name', 'active' and 'icon-name'
+ """
+ if pspec.name == "key":
+ return self._properties["key"]
+ elif pspec.name == "nick":
+ return self._properties["nick"]
+ elif pspec.name == "color":
+ return self._properties["color"]
+ elif pspec.name == "current-activity":
+ if not self._properties.has_key("current-activity"):
+ return None
+ curact = self._properties["current-activity"]
+ if not len(curact):
+ return None
+ for activity in self._activities.values():
+ if activity.props.id == curact:
+ return activity
+ return None
+ elif pspec.name == "owner":
+ return self._properties["owner"]
+ elif pspec.name == "icon":
+ if not self._icon:
+ self._icon = str(self._buddy.GetIcon(byte_arrays=True))
+ return self._icon
+ elif pspec.name == "ip4-address":
+ # IPv4 address will go away quite soon
+ if not self._properties.has_key("ip4-address"):
+ return None
+ return self._properties["ip4-address"]
+
+ def object_path(self):
+ """Retrieve our dbus object path"""
+ return self._object_path
+
+ def _emit_icon_changed_signal(self, bytes):
+ """Emit GObject signal when icon has changed"""
+ self._icon = str(bytes)
+ self.emit('icon-changed')
+ return False
+
+ def _icon_changed_cb(self, icon_data):
+ """Handle dbus signal by emitting a GObject signal"""
+ gobject.idle_add(self._emit_icon_changed_signal, icon_data)
+
+ def _emit_joined_activity_signal(self, object_path):
+ """Emit activity joined signal with Activity object"""
+ self.emit('joined-activity', self._ps_new_object(object_path))
+ return False
+
+ def _joined_activity_cb(self, object_path):
+ """Handle dbus signal by emitting a GObject signal
+
+ Stores the activity in activities dictionary as well
+ """
+ if not self._activities.has_key(object_path):
+ self._activities[object_path] = self._ps_new_object(object_path)
+ gobject.idle_add(self._emit_joined_activity_signal, object_path)
+
+ def _emit_left_activity_signal(self, object_path):
+ """Emit activity left signal with Activity object
+
+ XXX this calls self._ps_new_object instead of self._ps_del_object,
+ which would seem to be the incorrect callback?
+ """
+ self.emit('left-activity', self._ps_new_object(object_path))
+ return False
+
+ def _left_activity_cb(self, object_path):
+ """Handle dbus signal by emitting a GObject signal
+
+ Also removes from the activities dictionary
+ """
+ if self._activities.has_key(object_path):
+ del self._activities[object_path]
+ gobject.idle_add(self._emit_left_activity_signal, object_path)
+
+ def _handle_property_changed_signal(self, prop_list):
+ """Emit property-changed signal with property dictionary
+
+ Generates a property-changed signal with the results of
+ _get_properties_helper()
+ """
+ self._properties = self._get_properties_helper()
+ # FIXME: don't leak unexposed property names
+ self.emit('property-changed', prop_list)
+ return False
+
+ def _property_changed_cb(self, prop_list):
+ """Handle dbus signal by emitting a GObject signal"""
+ gobject.idle_add(self._handle_property_changed_signal, prop_list)
+
+ def get_icon_pixbuf(self):
+ """Retrieve Buddy's icon as a GTK pixel buffer
+
+ XXX Why aren't the icons coming in as SVG?
+ """
+ if self.props.icon and len(self.props.icon):
+ pbl = gtk.gdk.PixbufLoader()
+ pbl.write(self.props.icon)
+ pbl.close()
+ return pbl.get_pixbuf()
+ else:
+ return None
+
+ def get_joined_activities(self):
+ """Retrieve the set of all activities which this buddy has joined
+
+ Uses the GetJoinedActivities method on the service
+ object to produce object paths, wraps each in an
+ Activity object.
+
+ returns list of presence Activity objects
+ """
+ try:
+ resp = self._buddy.GetJoinedActivities()
+ except dbus.exceptions.DBusException:
+ return []
+ acts = []
+ for item in resp:
+ acts.append(self._ps_new_object(item))
+ return acts
diff --git a/sugar-toolkit/src/sugar/presence/presenceservice.py b/sugar-toolkit/src/sugar/presence/presenceservice.py
new file mode 100644
index 0000000..a7fd1a4
--- /dev/null
+++ b/sugar-toolkit/src/sugar/presence/presenceservice.py
@@ -0,0 +1,609 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+"""UI class to access system-level presence object
+
+STABLE.
+"""
+
+import logging
+import traceback
+
+import dbus
+import dbus.exceptions
+import dbus.glib
+import gobject
+
+from sugar.presence.buddy import Buddy
+from sugar.presence.activity import Activity
+
+
+DBUS_SERVICE = "org.laptop.Sugar.Presence"
+DBUS_INTERFACE = "org.laptop.Sugar.Presence"
+DBUS_PATH = "/org/laptop/Sugar/Presence"
+
+_logger = logging.getLogger('sugar.presence.presenceservice')
+
+
+class PresenceService(gobject.GObject):
+ """UI-side interface to the dbus presence service
+
+ This class provides UI programmers with simplified access
+ to the dbus service of the same name. It allows for observing
+ various events from the presence service as GObject events,
+ as well as some basic introspection queries.
+ """
+ __gsignals__ = {
+ 'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ # parameters: (activity: Activity, inviter: Buddy, message: unicode)
+ 'activity-invitation': (gobject.SIGNAL_RUN_FIRST, None, ([object]*3)),
+ 'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT, str])),
+ 'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT]))
+ }
+
+ _PS_BUDDY_OP = DBUS_PATH + "/Buddies/"
+ _PS_ACTIVITY_OP = DBUS_PATH + "/Activities/"
+
+
+ def __init__(self, allow_offline_iface=True):
+ """Initialise the service and attempt to connect to events
+ """
+ gobject.GObject.__init__(self)
+ self._objcache = {}
+ self._joined = None
+
+ # Get a connection to the session bus
+ self._bus = dbus.SessionBus()
+ self._bus.add_signal_receiver(self._name_owner_changed_cb,
+ signal_name="NameOwnerChanged",
+ dbus_interface="org.freedesktop.DBus")
+
+ # attempt to load the interface to the service...
+ self._allow_offline_iface = allow_offline_iface
+ self._get_ps()
+
+ def _name_owner_changed_cb(self, name, old, new):
+ if name != DBUS_SERVICE:
+ return
+ if (old and len(old)) and (not new and not len(new)):
+ # PS went away, clear out PS dbus service wrapper
+ self._ps_ = None
+ elif (not old and not len(old)) and (new and len(new)):
+ # PS started up
+ self._get_ps()
+
+ _ps_ = None
+ def _get_ps(self):
+ """Retrieve dbus interface to PresenceService
+
+ Also registers for updates from various dbus events on the
+ interface.
+
+ If unable to retrieve the interface, we will temporarily
+ return an _OfflineInterface object to allow the calling
+ code to continue functioning as though it had accessed a
+ real presence service.
+
+ If successful, caches the presence service interface
+ for use by other methods and returns that interface
+ """
+ if not self._ps_:
+ try:
+ # NOTE: We need to follow_name_owner_changes here
+ # because we can not connect to a signal unless
+ # we follow the changes or we start the service
+ # before we connect. Starting the service here
+ # causes a major bottleneck during startup
+ ps = dbus.Interface(
+ self._bus.get_object(DBUS_SERVICE,
+ DBUS_PATH,
+ follow_name_owner_changes=True),
+ DBUS_INTERFACE
+ )
+ except dbus.exceptions.DBusException, err:
+ _logger.error(
+ """Failure retrieving %r interface from
+ the D-BUS service %r %r: %s""",
+ DBUS_INTERFACE, DBUS_SERVICE, DBUS_PATH, err
+ )
+ if self._allow_offline_iface:
+ return _OfflineInterface()
+ raise RuntimeError("Failed to connect to the presence service.")
+ else:
+ self._ps_ = ps
+ ps.connect_to_signal('BuddyAppeared',
+ self._buddy_appeared_cb)
+ ps.connect_to_signal('BuddyDisappeared',
+ self._buddy_disappeared_cb)
+ ps.connect_to_signal('ActivityAppeared',
+ self._activity_appeared_cb)
+ ps.connect_to_signal('ActivityDisappeared',
+ self._activity_disappeared_cb)
+ ps.connect_to_signal('ActivityInvitation',
+ self._activity_invitation_cb)
+ ps.connect_to_signal('PrivateInvitation',
+ self._private_invitation_cb)
+ return self._ps_
+
+ _ps = property(
+ _get_ps, None, None,
+ """DBUS interface to the PresenceService
+ (services/presence/presenceservice)"""
+ )
+
+ def _new_object(self, object_path):
+ """Turn new object path into (cached) Buddy/Activity instance
+
+ object_path -- full dbus path of the new object, must be
+ prefixed with either of _PS_BUDDY_OP or _PS_ACTIVITY_OP
+
+ Note that this method is called throughout the class whenever
+ the representation of the object is required, it is not only
+ called when the object is first discovered. The point is to only have
+ _one_ Python object for any D-Bus object represented by an object path,
+ effectively wrapping the D-Bus object in a single Python GObject.
+
+ returns presence Buddy or Activity representation
+ """
+ obj = None
+ try:
+ obj = self._objcache[object_path]
+ _logger.debug('Reused proxy %r', obj)
+ except KeyError:
+ if object_path.startswith(self._PS_BUDDY_OP):
+ obj = Buddy(self._bus, self._new_object,
+ self._del_object, object_path)
+ elif object_path.startswith(self._PS_ACTIVITY_OP):
+ obj = Activity(self._bus, self._new_object,
+ self._del_object, object_path)
+ try:
+ # Pre-fill the activity's ID
+ activity_id = obj.props.id
+ except dbus.exceptions.DBusException:
+ logging.debug('Cannot get the activity ID')
+ else:
+ raise RuntimeError("Unknown object type")
+ self._objcache[object_path] = obj
+ _logger.debug('Created proxy %r', obj)
+ return obj
+
+ def _have_object(self, object_path):
+ return object_path in self._objcache.keys()
+
+ def _del_object(self, object_path):
+ """Fully remove an object from the object cache when
+ it's no longer needed.
+ """
+ del self._objcache[object_path]
+
+ def _emit_buddy_appeared_signal(self, object_path):
+ """Emit GObject event with presence.buddy.Buddy object"""
+ self.emit('buddy-appeared', self._new_object(object_path))
+ return False
+
+ def _buddy_appeared_cb(self, op):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_buddy_appeared_signal, op)
+
+ def _emit_buddy_disappeared_signal(self, object_path):
+ """Emit GObject event with presence.buddy.Buddy object"""
+ # Don't try to create a new object here if needed; it will probably
+ # fail anyway because the object has already been destroyed in the PS
+ if self._have_object(object_path):
+ obj = self._objcache[object_path]
+ self.emit('buddy-disappeared', obj)
+
+ # We cannot maintain the object in the cache because that would keep
+ # a lot of objects from being collected. That includes UI objects
+ # due to signals using strong references.
+ # If we want to cache some despite the memory usage increase,
+ # we could use a LRU cache limited to some value.
+ del self._objcache[object_path]
+ obj.destroy()
+
+ return False
+
+ def _buddy_disappeared_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_buddy_disappeared_signal, object_path)
+
+ def _emit_activity_invitation_signal(self, activity_path, buddy_path,
+ message):
+ """Emit GObject event with presence.activity.Activity object"""
+ self.emit('activity-invitation', self._new_object(activity_path),
+ self._new_object(buddy_path), unicode(message))
+ return False
+
+ def _activity_invitation_cb(self, activity_path, buddy_path, message):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_activity_invitation_signal, activity_path,
+ buddy_path, message)
+
+ def _emit_private_invitation_signal(self, bus_name, connection,
+ channel, chan_type):
+ """Emit GObject event with bus_name, connection and channel"""
+ self.emit('private-invitation', bus_name, connection,
+ channel, chan_type)
+ return False
+
+ def _private_invitation_cb(self, bus_name, connection, channel, chan_type):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_private_invitation_signal, bus_name,
+ connection, channel, chan_type)
+
+ def _emit_activity_appeared_signal(self, object_path):
+ """Emit GObject event with presence.activity.Activity object"""
+ self.emit('activity-appeared', self._new_object(object_path))
+ return False
+
+ def _activity_appeared_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_activity_appeared_signal, object_path)
+
+ def _emit_activity_disappeared_signal(self, object_path):
+ """Emit GObject event with presence.activity.Activity object"""
+ self.emit('activity-disappeared', self._new_object(object_path))
+ return False
+
+ def _activity_disappeared_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_activity_disappeared_signal, object_path)
+
+ def get(self, object_path):
+ """Return the Buddy or Activity object corresponding to the given
+ D-Bus object path.
+ """
+ return self._new_object(object_path)
+
+ def get_activities(self):
+ """Retrieve set of all activities from service
+
+ returns list of Activity objects for all object paths
+ the service reports exist (using GetActivities)
+ """
+ try:
+ resp = self._ps.GetActivities()
+ except dbus.exceptions.DBusException, err:
+ _logger.warn(
+ """Unable to retrieve activity list from presence service: %s"""
+ % err
+ )
+ return []
+ else:
+ acts = []
+ for item in resp:
+ acts.append(self._new_object(item))
+ return acts
+
+ def _get_activities_cb(self, reply_handler, resp):
+ acts = []
+ for item in resp:
+ acts.append(self._new_object(item))
+
+ reply_handler(acts)
+
+ def _get_activities_error_cb(self, error_handler, e):
+ if error_handler:
+ error_handler(e)
+ else:
+ _logger.warn(
+ """Unable to retrieve activity-list from presence service: %s"""
+ % e
+ )
+
+ def get_activities_async(self, reply_handler=None, error_handler=None):
+ """Retrieve set of all activities from service asyncronously
+ """
+
+ if not reply_handler:
+ logging.error('Function get_activities_async called without' \
+ 'a reply handler. Can not run.')
+ return
+
+ self._ps.GetActivities(
+ reply_handler=lambda resp: \
+ self._get_activities_cb(reply_handler, resp),
+ error_handler=lambda e: \
+ self._get_activities_error_cb(error_handler, e))
+
+
+ def get_activity(self, activity_id, warn_if_none=True):
+ """Retrieve single Activity object for the given unique id
+
+ activity_id -- unique ID for the activity
+
+ returns single Activity object or None if the activity
+ is not found using GetActivityById on the service
+ """
+ try:
+ act_op = self._ps.GetActivityById(activity_id)
+ except dbus.exceptions.DBusException, err:
+ if warn_if_none:
+ _logger.warn("Unable to retrieve activity handle for %r from "
+ "presence service: %s", activity_id, err)
+ return None
+ return self._new_object(act_op)
+
+ def get_buddies(self):
+ """Retrieve set of all buddies from service
+
+ returns list of Buddy objects for all object paths
+ the service reports exist (using GetBuddies)
+ """
+ try:
+ resp = self._ps.GetBuddies()
+ except dbus.exceptions.DBusException, err:
+ _logger.warn(
+ """Unable to retrieve buddy-list from presence service: %s"""
+ % err
+ )
+ return []
+ else:
+ buddies = []
+ for item in resp:
+ buddies.append(self._new_object(item))
+ return buddies
+
+ def _get_buddies_cb(self, reply_handler, resp):
+ buddies = []
+ for item in resp:
+ buddies.append(self._new_object(item))
+
+ reply_handler(buddies)
+
+ def _get_buddies_error_cb(self, error_handler, e):
+ if error_handler:
+ error_handler(e)
+ else:
+ _logger.warn(
+ """Unable to retrieve buddy-list from presence service: %s"""
+ % e
+ )
+
+ def get_buddies_async(self, reply_handler=None, error_handler=None):
+ """Retrieve set of all buddies from service asyncronously
+ """
+
+ if not reply_handler:
+ logging.error('Function get_buddies_async called without' \
+ 'a reply handler. Can not run.')
+ return
+
+ self._ps.GetBuddies(
+ reply_handler=lambda resp: \
+ self._get_buddies_cb(reply_handler, resp),
+ error_handler=lambda e: \
+ self._get_buddies_error_cb(error_handler, e))
+
+ def get_buddy(self, key):
+ """Retrieve single Buddy object for the given public key
+
+ key -- buddy's public encryption key
+
+ returns single Buddy object or None if the activity
+ is not found using GetBuddyByPublicKey on the
+ service
+ """
+ try:
+ buddy_op = self._ps.GetBuddyByPublicKey(dbus.ByteArray(key))
+ except dbus.exceptions.DBusException, err:
+ _logger.warn(
+ """Unable to retrieve buddy handle
+ for %r from presence service: %s"""
+ % key, err
+ )
+ return None
+ return self._new_object(buddy_op)
+
+ def get_buddy_by_telepathy_handle(self, tp_conn_name, tp_conn_path,
+ handle):
+ """Retrieve single Buddy object for the given public key
+
+ :Parameters:
+ `tp_conn_name` : str
+ The well-known bus name of a Telepathy connection
+ `tp_conn_path` : dbus.ObjectPath
+ The object path of the Telepathy connection
+ `handle` : int or long
+ The handle of a Telepathy contact on that connection,
+ of type HANDLE_TYPE_CONTACT. This may not be a
+ channel-specific handle.
+ :Returns: the Buddy object, or None if the buddy is not found
+ """
+ try:
+ buddy_op = self._ps.GetBuddyByTelepathyHandle(tp_conn_name,
+ tp_conn_path,
+ handle)
+ except dbus.exceptions.DBusException, err:
+ _logger.warn('Unable to retrieve buddy handle for handle %u at '
+ 'conn %s:%s from presence service: %s',
+ handle, tp_conn_name, tp_conn_path, err)
+ return None
+ return self._new_object(buddy_op)
+
+ def get_owner(self):
+ """Retrieves the laptop "owner" Buddy object."""
+ try:
+ owner_op = self._ps.GetOwner()
+ except dbus.exceptions.DBusException, err:
+ _logger.warn(
+ """Unable to retrieve local user/owner
+ from presence service: %s"""
+ % err
+ )
+ raise RuntimeError("Could not get owner object.")
+ return self._new_object(owner_op)
+
+ def _share_activity_cb(self, activity, op):
+ """Finish sharing the activity
+ """
+ # FIXME find a better way to shutup pylint
+ psact = self._new_object(op)
+ psact._joined = True
+ _logger.debug('%r: Just shared, setting up tubes', activity)
+ psact.set_up_tubes(reply_handler=lambda:
+ self.emit("activity-shared", True, psact, None),
+ error_handler=lambda e:
+ self._share_activity_error_cb(activity, e))
+
+ def _share_activity_error_cb(self, activity, err):
+ """Notify with GObject event of unsuccessful sharing of activity"""
+ _logger.debug("Error sharing activity %s: %s" %
+ (activity.get_id(), err))
+ self.emit("activity-shared", False, None, err)
+
+ def share_activity(self, activity, properties=None, private=True):
+ """Ask presence service to ask the activity to share itself publicly.
+
+ Uses the AdvertiseActivity method on the service to ask for the
+ sharing of the given activity. Arranges to emit activity-shared
+ event with:
+
+ (success, Activity, err)
+
+ on success/failure.
+
+ returns None
+ """
+ actid = activity.get_id()
+
+ if properties is None:
+ properties = {}
+
+ # Ensure the activity is not already shared/joined
+ for obj in self._objcache.values():
+ if not isinstance(object, Activity):
+ continue
+ if obj.props.id == actid or obj.props.joined:
+ raise RuntimeError("Activity %s is already shared." %
+ actid)
+
+ atype = activity.get_bundle_id()
+ name = activity.props.title
+ properties['private'] = bool(private)
+ self._ps.ShareActivity(actid, atype, name, properties,
+ reply_handler=lambda op: \
+ self._share_activity_cb(activity, op),
+ error_handler=lambda e: \
+ self._share_activity_error_cb(activity, e))
+
+ def get_preferred_connection(self):
+ """Gets the preferred telepathy connection object that an activity
+ should use when talking directly to telepathy
+
+ returns the bus name and the object path of the Telepathy connection"""
+
+ try:
+ bus_name, object_path = self._ps.GetPreferredConnection()
+ except dbus.exceptions.DBusException:
+ logging.error(traceback.format_exc())
+ return None
+
+ return bus_name, object_path
+
+class _OfflineInterface( object ):
+ """Offline-presence-service interface
+
+ Used to mimic the behaviour of a real PresenceService sufficiently
+ to avoid crashing client code that expects the given interface.
+
+ XXX we could likely return a "MockOwner" object reasonably
+ easily, but would it be worth it?
+ """
+ def raiseException( self, *args, **named ):
+ """Raise dbus.exceptions.DBusException"""
+ raise dbus.exceptions.DBusException(
+ """PresenceService Interface not available"""
+ )
+ GetActivities = raiseException
+ GetActivityById = raiseException
+ GetBuddies = raiseException
+ GetBuddyByPublicKey = raiseException
+ GetOwner = raiseException
+ GetPreferredConnection = raiseException
+ def ShareActivity(
+ self, actid, atype, name, properties,
+ reply_handler, error_handler,
+ ):
+ """Pretend to share and fail..."""
+ exc = IOError(
+ """Unable to share activity as PresenceService
+ is not currenly available"""
+ )
+ return error_handler( exc )
+
+class _MockPresenceService(gobject.GObject):
+ """Test fixture allowing testing of items that use PresenceService
+
+ See PresenceService for usage and purpose
+ """
+ __gsignals__ = {
+ 'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT])),
+ 'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ def get_activities(self):
+ return []
+
+ def get_activity(self, activity_id):
+ return None
+
+ def get_buddies(self):
+ return []
+
+ def get_buddy(self, key):
+ return None
+
+ def get_owner(self):
+ return None
+
+ def share_activity(self, activity, properties=None):
+ return None
+
+_ps = None
+def get_instance(allow_offline_iface=False):
+ """Retrieve this process' view of the PresenceService"""
+ global _ps
+ if not _ps:
+ _ps = PresenceService(allow_offline_iface)
+ return _ps
+
diff --git a/sugar-toolkit/src/sugar/presence/sugartubeconn.py b/sugar-toolkit/src/sugar/presence/sugartubeconn.py
new file mode 100644
index 0000000..954ef67
--- /dev/null
+++ b/sugar-toolkit/src/sugar/presence/sugartubeconn.py
@@ -0,0 +1,63 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""Subclass of TubeConnection that converts handles to Sugar Buddies
+
+STABLE.
+"""
+
+from telepathy.constants import (
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES)
+
+from sugar.presence.tubeconn import TubeConnection
+from sugar.presence import presenceservice
+
+
+class SugarTubeConnection(TubeConnection):
+ """Subclass of TubeConnection that converts handles to Sugar Buddies"""
+
+ def __new__(cls, conn, tubes_iface, tube_id, address=None,
+ group_iface=None, mainloop=None):
+ self = super(SugarTubeConnection, cls).__new__(
+ cls, conn, tubes_iface, tube_id, address=address,
+ group_iface=group_iface, mainloop=mainloop)
+ self._conn = conn
+ self._group_iface = group_iface
+ return self
+
+ def get_buddy(self, cs_handle):
+ """Retrieve a Buddy object given a telepathy handle.
+
+ cs_handle: A channel-specific CONTACT type handle.
+ returns: sugar.presence Buddy object or None
+ """
+ pservice = presenceservice.get_instance()
+ if self.self_handle == cs_handle:
+ # It's me, just get my global handle
+ handle = self._conn.GetSelfHandle()
+ elif self._group_iface.GetGroupFlags() & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ # The group (channel) has channel specific handles
+ handle = self._group_iface.GetHandleOwners([cs_handle])[0]
+ else:
+ # The group does not have channel specific handles
+ handle = cs_handle
+
+ # deal with failure to get the handle owner
+ if handle == 0:
+ return None
+ return pservice.get_buddy_by_telepathy_handle(
+ self._conn.service_name, self._conn.object_path, handle)
diff --git a/sugar-toolkit/src/sugar/presence/test_presence.txt b/sugar-toolkit/src/sugar/presence/test_presence.txt
new file mode 100644
index 0000000..d0736a9
--- /dev/null
+++ b/sugar-toolkit/src/sugar/presence/test_presence.txt
@@ -0,0 +1,26 @@
+This is a test of presence.
+
+To test this service we will start up a mock dbus library:
+
+ >>> from sugar.testing import mockdbus
+ >>> import dbus
+ >>> pres_service = mockdbus.MockService(
+ ... 'org.laptop.Presence', '/org/laptop/Presence', name='pres')
+ >>> pres_service.install()
+ >>> pres_interface = dbus.Interface(pres_service, 'org.laptop.Presence')
+
+Then we import the library (second, to make sure it connects to our
+mocked system, though the lazy instantiation in get_instance() should
+handle it):
+
+ >>> from sugar.presence import PresenceService
+ >>> ps = PresenceService.get_instance()
+ >>> pres_interface.make_response('getServices', [])
+ >>> ps.get_services()
+ Called pres.org.laptop.Presence:getServices()
+ []
+ >>> pres_interface.make_response('getBuddies', [])
+ >>> ps.get_buddies()
+ Called pres.org.laptop.Presence:getBuddies()
+ []
+
diff --git a/sugar-toolkit/src/sugar/presence/tubeconn.py b/sugar-toolkit/src/sugar/presence/tubeconn.py
new file mode 100644
index 0000000..8606db6
--- /dev/null
+++ b/sugar-toolkit/src/sugar/presence/tubeconn.py
@@ -0,0 +1,114 @@
+# This should eventually land in telepathy-python, so has the same license:
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+STABLE.
+"""
+
+__all__ = ('TubeConnection',)
+__docformat__ = 'reStructuredText'
+
+
+import logging
+
+from dbus.connection import Connection
+
+
+logger = logging.getLogger('telepathy.tubeconn')
+
+
+class TubeConnection(Connection):
+
+ # pylint: disable-msg=W0212
+ # Confused by __new__
+ def __new__(cls, conn, tubes_iface, tube_id, address=None,
+ group_iface=None, mainloop=None):
+ if address is None:
+ address = tubes_iface.GetDBusTubeAddress(tube_id)
+ self = super(TubeConnection, cls).__new__(cls, address,
+ mainloop=mainloop)
+
+ self._tubes_iface = tubes_iface
+ self.tube_id = tube_id
+ self.participants = {}
+ self.bus_name_to_handle = {}
+ self._mapping_watches = []
+
+ if group_iface is None:
+ method = conn.GetSelfHandle
+ else:
+ method = group_iface.GetSelfHandle
+ method(reply_handler=self._on_get_self_handle_reply,
+ error_handler=self._on_get_self_handle_error)
+
+ return self
+
+ # pylint: disable-msg=W0201
+ # Confused by __new__
+ def _on_get_self_handle_reply(self, handle):
+ self.self_handle = handle
+ match = self._tubes_iface.connect_to_signal('DBusNamesChanged',
+ self._on_dbus_names_changed)
+ self._tubes_iface.GetDBusNames(self.tube_id,
+ reply_handler=self._on_get_dbus_names_reply,
+ error_handler=self._on_get_dbus_names_error)
+ self._dbus_names_changed_match = match
+
+ def _on_get_self_handle_error(self, e):
+ logging.basicConfig()
+ logger.error('GetSelfHandle failed: %s', e)
+
+ def close(self):
+ self._dbus_names_changed_match.remove()
+ self._on_dbus_names_changed(self.tube_id, (), self.participants.keys())
+ super(TubeConnection, self).close()
+
+ def _on_get_dbus_names_reply(self, names):
+ self._on_dbus_names_changed(self.tube_id, names, ())
+
+ def _on_get_dbus_names_error(self, e):
+ logging.basicConfig()
+ logger.error('GetDBusNames failed: %s', e)
+
+ def _on_dbus_names_changed(self, tube_id, added, removed):
+ if tube_id == self.tube_id:
+ for handle, bus_name in added:
+ if handle == self.self_handle:
+ # I've just joined - set my unique name
+ self.set_unique_name(bus_name)
+ self.participants[handle] = bus_name
+ self.bus_name_to_handle[bus_name] = handle
+
+ # call the callback while the removed people are still in
+ # participants, so their bus names are available
+ for callback in self._mapping_watches:
+ callback(added, removed)
+
+ for handle in removed:
+ bus_name = self.participants.pop(handle, None)
+ self.bus_name_to_handle.pop(bus_name, None)
+
+ def watch_participants(self, callback):
+ self._mapping_watches.append(callback)
+ if self.participants:
+ # GetDBusNames already returned: fake a participant add event
+ # immediately
+ added = []
+ for k, v in self.participants.iteritems():
+ added.append((k, v))
+ callback(added, [])